From bd0683d1017dea4cbd41a545f55008581d3a3630 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 11 Dec 2015 15:26:40 +0100 Subject: [PATCH 01/82] Generalised input framework for raw mice, gamepads, trackers (incl. HMDs like Oculus Rift) --- makepanda/makepanda.py | 30 +- panda/src/device/analogNode.I | 15 +- panda/src/device/analogNode.cxx | 25 +- panda/src/device/analogNode.h | 3 +- panda/src/device/buttonNode.I | 26 +- panda/src/device/buttonNode.cxx | 42 +- panda/src/device/buttonNode.h | 6 +- panda/src/device/clientAnalogDevice.I | 74 --- panda/src/device/clientAnalogDevice.cxx | 47 +- panda/src/device/clientAnalogDevice.h | 23 - panda/src/device/clientBase.cxx | 1 - panda/src/device/clientButtonDevice.I | 110 ---- panda/src/device/clientButtonDevice.cxx | 113 +--- panda/src/device/clientButtonDevice.h | 38 -- panda/src/device/clientDevice.I | 53 -- panda/src/device/clientDevice.cxx | 25 +- panda/src/device/clientDevice.h | 27 +- panda/src/device/clientTrackerDevice.I | 14 +- panda/src/device/clientTrackerDevice.h | 6 - panda/src/device/config_device.cxx | 11 +- panda/src/device/dialNode.I | 15 +- panda/src/device/evdevInputDevice.I | 14 + panda/src/device/evdevInputDevice.cxx | 424 ++++++++++++++ panda/src/device/evdevInputDevice.h | 63 +++ panda/src/device/inputDevice.I | 445 +++++++++++++++ panda/src/device/inputDevice.cxx | 517 ++++++++++++++++++ panda/src/device/inputDevice.h | 279 ++++++++++ panda/src/device/inputDeviceManager.I | 27 + panda/src/device/inputDeviceManager.cxx | 240 ++++++++ panda/src/device/inputDeviceManager.h | 64 +++ panda/src/device/linuxJoystickDevice.I | 14 + panda/src/device/linuxJoystickDevice.cxx | 313 +++++++++++ panda/src/device/linuxJoystickDevice.h | 60 ++ panda/src/device/p3device_composite1.cxx | 5 +- panda/src/device/p3device_composite2.cxx | 1 - panda/src/device/trackerNode.I | 3 +- panda/src/device/trackerNode.cxx | 19 +- panda/src/device/trackerNode.h | 16 +- panda/src/display/callbackGraphicsWindow.cxx | 20 +- panda/src/display/callbackGraphicsWindow.h | 1 - panda/src/display/config_display.cxx | 6 +- panda/src/display/graphicsWindow.cxx | 117 +--- panda/src/display/graphicsWindow.h | 18 +- panda/src/display/graphicsWindowInputDevice.I | 232 +------- .../src/display/graphicsWindowInputDevice.cxx | 217 +------- panda/src/display/graphicsWindowInputDevice.h | 134 ++--- .../{device => display}/mouseAndKeyboard.cxx | 29 +- .../{device => display}/mouseAndKeyboard.h | 3 +- panda/src/display/p3display_composite2.cxx | 1 + panda/src/putil/config_util.cxx | 2 + panda/src/putil/gamepadButton.cxx | 75 +++ panda/src/putil/gamepadButton.h | 57 ++ panda/src/putil/p3putil_composite1.cxx | 1 - panda/src/putil/p3putil_composite2.cxx | 2 + .../src/tinydisplay/tinyOsxGraphicsWindow.mm | 32 +- .../src/tinydisplay/tinySDLGraphicsWindow.cxx | 20 +- panda/src/tinydisplay/tinyXGraphicsWindow.cxx | 20 +- panda/src/vrpn/vrpnAnalog.cxx | 2 - panda/src/vrpn/vrpnButton.cxx | 2 - panda/src/vrpn/vrpnDial.cxx | 2 - panda/src/vrpn/vrpnTracker.cxx | 31 +- panda/src/windisplay/winGraphicsWindow.cxx | 6 +- panda/src/x11display/x11GraphicsWindow.cxx | 347 +++--------- panda/src/x11display/x11GraphicsWindow.h | 2 +- 64 files changed, 2969 insertions(+), 1618 deletions(-) create mode 100644 panda/src/device/evdevInputDevice.I create mode 100644 panda/src/device/evdevInputDevice.cxx create mode 100644 panda/src/device/evdevInputDevice.h create mode 100644 panda/src/device/inputDevice.I create mode 100644 panda/src/device/inputDevice.cxx create mode 100644 panda/src/device/inputDevice.h create mode 100644 panda/src/device/inputDeviceManager.I create mode 100644 panda/src/device/inputDeviceManager.cxx create mode 100644 panda/src/device/inputDeviceManager.h create mode 100644 panda/src/device/linuxJoystickDevice.I create mode 100644 panda/src/device/linuxJoystickDevice.cxx create mode 100644 panda/src/device/linuxJoystickDevice.h rename panda/src/{device => display}/mouseAndKeyboard.cxx (88%) rename panda/src/{device => display}/mouseAndKeyboard.h (98%) create mode 100644 panda/src/putil/gamepadButton.cxx create mode 100644 panda/src/putil/gamepadButton.h diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 8fe92440bc..d11d0825bd 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -3641,6 +3641,21 @@ if (not RUNTIME): TargetAdd('libp3dgraph.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dgraph', 'SRCDIR:panda/src/dgraph']) TargetAdd('libp3dgraph_igate.obj', input='libp3dgraph.in', opts=["DEPENDENCYONLY"]) +# +# DIRECTORY: panda/src/device/ +# + +if (not RUNTIME): + OPTS=['DIR:panda/src/device', 'BUILDING:PANDA'] + TargetAdd('p3device_composite1.obj', opts=OPTS, input='p3device_composite1.cxx') + TargetAdd('p3device_composite2.obj', opts=OPTS, input='p3device_composite2.cxx') + + OPTS=['DIR:panda/src/device'] + IGATEFILES=GetDirectoryContents('panda/src/device', ["*.h", "*_composite*.cxx"]) + TargetAdd('libp3device.in', opts=OPTS, input=IGATEFILES) + TargetAdd('libp3device.in', opts=['IMOD:panda3d.core', 'ILIB:libp3device', 'SRCDIR:panda/src/device']) + TargetAdd('libp3device_igate.obj', input='libp3device.in', opts=["DEPENDENCYONLY"]) + # # DIRECTORY: panda/src/display/ # @@ -3665,21 +3680,6 @@ if (not RUNTIME): TargetAdd('subprocessWindowBuffer.obj', opts=OPTS, input='subprocessWindowBuffer.cxx') TargetAdd('libp3subprocbuffer.ilb', input='subprocessWindowBuffer.obj') -# -# DIRECTORY: panda/src/device/ -# - -if (not RUNTIME): - OPTS=['DIR:panda/src/device', 'BUILDING:PANDA'] - TargetAdd('p3device_composite1.obj', opts=OPTS, input='p3device_composite1.cxx') - TargetAdd('p3device_composite2.obj', opts=OPTS, input='p3device_composite2.cxx') - - OPTS=['DIR:panda/src/device'] - IGATEFILES=GetDirectoryContents('panda/src/device', ["*.h", "*_composite*.cxx"]) - TargetAdd('libp3device.in', opts=OPTS, input=IGATEFILES) - TargetAdd('libp3device.in', opts=['IMOD:panda3d.core', 'ILIB:libp3device', 'SRCDIR:panda/src/device']) - TargetAdd('libp3device_igate.obj', input='libp3device.in', opts=["DEPENDENCYONLY"]) - # # DIRECTORY: panda/src/pnmtext/ # diff --git a/panda/src/device/analogNode.I b/panda/src/device/analogNode.I index 9747587b73..ebc382fce3 100644 --- a/panda/src/device/analogNode.I +++ b/panda/src/device/analogNode.I @@ -44,10 +44,7 @@ is_valid() const { //////////////////////////////////////////////////////////////////// INLINE int AnalogNode:: get_num_controls() const { - _analog->acquire(); - int result = _analog->get_num_controls(); - _analog->unlock(); - return result; + return _analog->get_num_controls(); } //////////////////////////////////////////////////////////////////// @@ -60,10 +57,7 @@ get_num_controls() const { //////////////////////////////////////////////////////////////////// INLINE double AnalogNode:: get_control_state(int index) const { - _analog->acquire(); - double result = _analog->get_control_state(index); - _analog->unlock(); - return result; + return _analog->get_control_state(index); } //////////////////////////////////////////////////////////////////// @@ -75,10 +69,7 @@ get_control_state(int index) const { //////////////////////////////////////////////////////////////////// INLINE bool AnalogNode:: is_control_known(int index) const { - _analog->acquire(); - bool result = _analog->is_control_known(index); - _analog->unlock(); - return result; + return _analog->is_control_known(index); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/device/analogNode.cxx b/panda/src/device/analogNode.cxx index 1bea47ceb2..6d2cef3f67 100644 --- a/panda/src/device/analogNode.cxx +++ b/panda/src/device/analogNode.cxx @@ -30,7 +30,7 @@ AnalogNode(ClientBase *client, const string &device_name) : DataNode(device_name) { _xy_output = define_output("xy", EventStoreVec2::get_class_type()); - _xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); + _xy = new EventStoreVec2(LPoint2(0)); nassertv(client != (ClientBase *)NULL); PT(ClientDevice) device = @@ -49,7 +49,23 @@ AnalogNode(ClientBase *client, const string &device_name) : return; } - _analog = DCAST(ClientAnalogDevice, device); + _analog = device; +} + +//////////////////////////////////////////////////////////////////// +// Function: AnalogNode::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +AnalogNode:: +AnalogNode(InputDevice *device) : + DataNode(device->get_name()), + _analog(device) +{ + _xy_output = define_output("xy", EventStoreVec2::get_class_type()); + _xy = new EventStoreVec2(LPoint2(0)); + + nassertv(device != (InputDevice *)NULL); } //////////////////////////////////////////////////////////////////// @@ -74,9 +90,7 @@ write(ostream &out, int indent_level) const { DataNode::write(out, indent_level); if (_analog != (ClientAnalogDevice *)NULL) { - _analog->acquire(); _analog->write_controls(out, indent_level + 2); - _analog->unlock(); } } @@ -100,8 +114,6 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, _analog->poll(); LPoint2 out(0.0f, 0.0f); - - _analog->acquire(); for (int i = 0; i < max_outputs; i++) { if (_outputs[i]._index >= 0 && _analog->is_control_known(_outputs[i]._index)) { @@ -112,7 +124,6 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, } } } - _analog->unlock(); _xy->set_value(out); output.set_data(_xy_output, EventParameter(_xy)); } diff --git a/panda/src/device/analogNode.h b/panda/src/device/analogNode.h index 216c4ae0d4..ea0a6c573e 100644 --- a/panda/src/device/analogNode.h +++ b/panda/src/device/analogNode.h @@ -45,6 +45,7 @@ class EXPCL_PANDA_DEVICE AnalogNode : public DataNode { PUBLISHED: AnalogNode(ClientBase *client, const string &device_name); + AnalogNode(InputDevice *device); virtual ~AnalogNode(); INLINE bool is_valid() const; @@ -73,7 +74,7 @@ private: enum { max_outputs = 2 }; OutputData _outputs[max_outputs]; - PT(ClientAnalogDevice) _analog; + PT(InputDevice) _analog; protected: // Inherited from DataNode diff --git a/panda/src/device/buttonNode.I b/panda/src/device/buttonNode.I index 27faac8325..2eed86eb26 100644 --- a/panda/src/device/buttonNode.I +++ b/panda/src/device/buttonNode.I @@ -21,7 +21,7 @@ //////////////////////////////////////////////////////////////////// INLINE bool ButtonNode:: is_valid() const { - return (_button != (ClientButtonDevice *)NULL) && _button->is_connected(); + return (_device != (ClientButtonDevice *)NULL) && _device->is_connected(); } //////////////////////////////////////////////////////////////////// @@ -36,10 +36,7 @@ is_valid() const { //////////////////////////////////////////////////////////////////// INLINE int ButtonNode:: get_num_buttons() const { - _button->acquire(); - int result = _button->get_num_buttons(); - _button->unlock(); - return result; + return _device->get_num_buttons(); } //////////////////////////////////////////////////////////////////// @@ -58,9 +55,7 @@ get_num_buttons() const { //////////////////////////////////////////////////////////////////// INLINE void ButtonNode:: set_button_map(int index, ButtonHandle button) { - _button->acquire(); - _button->set_button_map(index, button); - _button->unlock(); + _device->set_button_map(index, button); } //////////////////////////////////////////////////////////////////// @@ -73,10 +68,7 @@ set_button_map(int index, ButtonHandle button) { //////////////////////////////////////////////////////////////////// INLINE ButtonHandle ButtonNode:: get_button_map(int index) const { - _button->acquire(); - ButtonHandle result = _button->get_button_map(index); - _button->unlock(); - return result; + return _device->get_button_map(index); } //////////////////////////////////////////////////////////////////// @@ -88,10 +80,7 @@ get_button_map(int index) const { //////////////////////////////////////////////////////////////////// INLINE bool ButtonNode:: get_button_state(int index) const { - _button->acquire(); - bool result = _button->get_button_state(index); - _button->unlock(); - return result; + return _device->get_button_state(index); } //////////////////////////////////////////////////////////////////// @@ -103,8 +92,5 @@ get_button_state(int index) const { //////////////////////////////////////////////////////////////////// INLINE bool ButtonNode:: is_button_known(int index) const { - _button->acquire(); - bool result = _button->is_button_known(index); - _button->unlock(); - return result; + return _device->is_button_known(index); } diff --git a/panda/src/device/buttonNode.cxx b/panda/src/device/buttonNode.cxx index ebae5309a3..d0306b915e 100644 --- a/panda/src/device/buttonNode.cxx +++ b/panda/src/device/buttonNode.cxx @@ -30,7 +30,6 @@ ButtonNode(ClientBase *client, const string &device_name) : DataNode(device_name) { _button_events_output = define_output("button_events", ButtonEventList::get_class_type()); - _button_events = new ButtonEventList; nassertv(client != (ClientBase *)NULL); PT(ClientDevice) device = @@ -49,7 +48,21 @@ ButtonNode(ClientBase *client, const string &device_name) : return; } - _button = DCAST(ClientButtonDevice, device); + _device = device; +} + +//////////////////////////////////////////////////////////////////// +// Function: ButtonNode::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +ButtonNode:: +ButtonNode(InputDevice *device) : + DataNode(device->get_name()), + _device(device) +{ + _button_events_output = define_output("button_events", ButtonEventList::get_class_type()); + _device = device; } //////////////////////////////////////////////////////////////////// @@ -59,7 +72,7 @@ ButtonNode(ClientBase *client, const string &device_name) : //////////////////////////////////////////////////////////////////// ButtonNode:: ~ButtonNode() { - // When the _button pointer destructs, the ClientButtonDevice + // When the _device pointer destructs, the ClientButtonDevice // disconnects itself from the ClientBase, and everything that needs // to get turned off does. Magic. } @@ -73,11 +86,9 @@ void ButtonNode:: output(ostream &out) const { DataNode::output(out); - if (_button != (ClientButtonDevice *)NULL) { + if (_device != (InputDevice *)NULL) { out << " ("; - _button->acquire(); - _button->output_buttons(out); - _button->unlock(); + _device->output_buttons(out); out << ")"; } } @@ -91,10 +102,8 @@ void ButtonNode:: write(ostream &out, int indent_level) const { DataNode::write(out, indent_level); - if (_button != (ClientButtonDevice *)NULL) { - _button->acquire(); - _button->write_buttons(out, indent_level + 2); - _button->unlock(); + if (_device != (InputDevice *)NULL) { + _device->write_buttons(out, indent_level + 2); } } @@ -115,14 +124,7 @@ void ButtonNode:: do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { if (is_valid()) { - _button->poll(); - _button->acquire(); - - (*_button_events) = (*_button->get_button_events()); - - _button->get_button_events()->clear(); - _button->unlock(); - - output.set_data(_button_events_output, EventParameter(_button_events)); + PT(ButtonEventList) bel = _device->get_button_events(); + output.set_data(_button_events_output, EventParameter(bel)); } } diff --git a/panda/src/device/buttonNode.h b/panda/src/device/buttonNode.h index 43f04b954e..349e76e708 100644 --- a/panda/src/device/buttonNode.h +++ b/panda/src/device/buttonNode.h @@ -20,8 +20,6 @@ #include "clientBase.h" #include "clientButtonDevice.h" #include "dataNode.h" -#include "buttonEventList.h" - //////////////////////////////////////////////////////////////////// // Class : ButtonNode @@ -41,6 +39,7 @@ class EXPCL_PANDA_DEVICE ButtonNode : public DataNode { PUBLISHED: ButtonNode(ClientBase *client, const string &device_name); + ButtonNode(InputDevice *device); virtual ~ButtonNode(); INLINE bool is_valid() const; @@ -58,7 +57,7 @@ public: virtual void write(ostream &out, int indent_level = 0) const; private: - PT(ClientButtonDevice) _button; + PT(InputDevice) _device; protected: // Inherited from DataNode @@ -69,7 +68,6 @@ protected: private: // outputs int _button_events_output; - PT(ButtonEventList) _button_events; public: static TypeHandle get_class_type() { diff --git a/panda/src/device/clientAnalogDevice.I b/panda/src/device/clientAnalogDevice.I index c36619d4f1..3877364de9 100644 --- a/panda/src/device/clientAnalogDevice.I +++ b/panda/src/device/clientAnalogDevice.I @@ -13,18 +13,6 @@ //////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::AnalogState::Constructor -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE ClientAnalogDevice::AnalogState:: -AnalogState() : - _state(0.0), - _known(false) -{ -} - //////////////////////////////////////////////////////////////////// // Function: ClientAnalogDevice::Constructor // Access: Protected @@ -35,65 +23,3 @@ ClientAnalogDevice(ClientBase *client, const string &device_name): ClientDevice(client, get_class_type(), device_name) { } - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::get_num_controls -// Access: Public -// Description: Returns the number of analog controls known to the -// ClientAnalogDevice. This number may change as -// more controls are discovered. -//////////////////////////////////////////////////////////////////// -INLINE int ClientAnalogDevice:: -get_num_controls() const { - return _controls.size(); -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::set_control_state -// Access: Public -// Description: Sets the state of the indicated analog index. The -// caller should ensure that acquire() is in effect while -// this call is made. This should be a number in the -// range -1.0 to 1.0, representing the current position -// of the control within its total range of movement. -//////////////////////////////////////////////////////////////////// -INLINE void ClientAnalogDevice:: -set_control_state(int index, double state) { - ensure_control_index(index); - nassertv(index >= 0 && index < (int)_controls.size()); - _controls[index]._state = state; - _controls[index]._known = true; -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::get_control_state -// Access: Public -// Description: Returns the current position of indicated analog -// control (identified by its index number), or 0.0 if -// the control is unknown. The normal range of a single -// control is -1.0 to 1.0. -//////////////////////////////////////////////////////////////////// -INLINE double ClientAnalogDevice:: -get_control_state(int index) const { - if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._state; - } else { - return 0.0; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::is_control_known -// Access: Public -// Description: Returns true if the state of the indicated analog -// control is known, or false if we have never heard -// anything about this particular control. -//////////////////////////////////////////////////////////////////// -INLINE bool ClientAnalogDevice:: -is_control_known(int index) const { - if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._known; - } else { - return false; - } -} diff --git a/panda/src/device/clientAnalogDevice.cxx b/panda/src/device/clientAnalogDevice.cxx index 1dbe277c82..6d4a84de23 100644 --- a/panda/src/device/clientAnalogDevice.cxx +++ b/panda/src/device/clientAnalogDevice.cxx @@ -19,25 +19,6 @@ TypeHandle ClientAnalogDevice::_type_handle; - - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::ensure_control_index -// Access: Private -// Description: Guarantees that there is a slot in the array for the -// indicated index number, by filling the array up to -// that index if necessary. -//////////////////////////////////////////////////////////////////// -void ClientAnalogDevice:: -ensure_control_index(int index) { - nassertv(index >= 0); - - _controls.reserve(index + 1); - while ((int)_controls.size() <= index) { - _controls.push_back(AnalogState()); - } -} - //////////////////////////////////////////////////////////////////// // Function: ClientAnalogDevice::write // Access: Public, Virtual @@ -45,32 +26,6 @@ ensure_control_index(int index) { //////////////////////////////////////////////////////////////////// void ClientAnalogDevice:: write(ostream &out, int indent_level) const { - indent(out, indent_level) << get_type() << " " << get_device_name() << ":\n"; + indent(out, indent_level) << get_type() << " " << get_name() << ":\n"; write_controls(out, indent_level + 2); } - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::write_analogs -// Access: Public -// Description: Writes a multi-line description of the current analog -// control states. -//////////////////////////////////////////////////////////////////// -void ClientAnalogDevice:: -write_controls(ostream &out, int indent_level) const { - bool any_controls = false; - Controls::const_iterator ai; - for (ai = _controls.begin(); ai != _controls.end(); ++ai) { - const AnalogState &state = (*ai); - if (state._known) { - any_controls = true; - - indent(out, indent_level) - << (int)(ai - _controls.begin()) << ". " << state._state << "\n"; - } - } - - if (!any_controls) { - indent(out, indent_level) - << "(no known analog controls)\n"; - } -} diff --git a/panda/src/device/clientAnalogDevice.h b/panda/src/device/clientAnalogDevice.h index 4db8fa53c4..c79daa8b16 100644 --- a/panda/src/device/clientAnalogDevice.h +++ b/panda/src/device/clientAnalogDevice.h @@ -36,30 +36,7 @@ protected: INLINE ClientAnalogDevice(ClientBase *client, const string &device_name); public: - INLINE int get_num_controls() const; - - INLINE void set_control_state(int index, double state); - INLINE double get_control_state(int index) const; - INLINE bool is_control_known(int index) const; - virtual void write(ostream &out, int indent_level = 0) const; - void write_controls(ostream &out, int indent_level) const; - -private: - void ensure_control_index(int index); - -protected: - class AnalogState { - public: - INLINE AnalogState(); - - double _state; - bool _known; - }; - - typedef pvector Controls; - Controls _controls; - public: static TypeHandle get_class_type() { diff --git a/panda/src/device/clientBase.cxx b/panda/src/device/clientBase.cxx index e01c35766b..12c570c28c 100644 --- a/panda/src/device/clientBase.cxx +++ b/panda/src/device/clientBase.cxx @@ -144,7 +144,6 @@ get_device(TypeHandle device_type, const string &device_name) { if (device != (ClientDevice *)NULL) { dbn.insert(DevicesByName::value_type(device_name, device)); - device->_is_connected = true; } return device; diff --git a/panda/src/device/clientButtonDevice.I b/panda/src/device/clientButtonDevice.I index 21308006c9..e039e28421 100644 --- a/panda/src/device/clientButtonDevice.I +++ b/panda/src/device/clientButtonDevice.I @@ -12,113 +12,3 @@ // //////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::ButtonState::Constructor -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE ClientButtonDevice::ButtonState:: -ButtonState() : - _handle(ButtonHandle::none()), - _state(S_unknown) -{ -} - - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::get_num_buttons -// Access: Public -// Description: Returns the number of buttons known to the -// ClientButtonDevice. This includes those buttons -// whose state has been seen, as well as buttons that -// have been associated with a ButtonHandle even if -// their state is unknown. This number may change as -// more buttons are discovered. -//////////////////////////////////////////////////////////////////// -INLINE int ClientButtonDevice:: -get_num_buttons() const { - return _buttons.size(); -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::set_button_map -// Access: Public -// Description: Associates the indicated ButtonHandle with the button -// of the indicated index number. When the given button -// index changes state, a corresponding ButtonEvent will -// be generated with the given ButtonHandle. Pass -// ButtonHandle::none() to turn off any association. -// -// It is not necessary to call this if you simply want -// to query the state of the various buttons by index -// number; this is only necessary in order to generate -// ButtonEvents when the buttons change state. -//////////////////////////////////////////////////////////////////// -INLINE void ClientButtonDevice:: -set_button_map(int index, ButtonHandle button) { - ensure_button_index(index); - nassertv(index >= 0 && index < (int)_buttons.size()); - _buttons[index]._handle = button; -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::get_button_map -// Access: Public -// Description: Returns the ButtonHandle that was previously -// associated with the given index number by -// a call to set_button_map(), or ButtonHandle::none() -// if no button was associated. -//////////////////////////////////////////////////////////////////// -INLINE ButtonHandle ClientButtonDevice:: -get_button_map(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { - return _buttons[index]._handle; - } else { - return ButtonHandle::none(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::get_button_state -// Access: Public -// Description: Returns true if the indicated button (identified by -// its index number) is currently known to be down, or -// false if it is up or unknown. -//////////////////////////////////////////////////////////////////// -INLINE bool ClientButtonDevice:: -get_button_state(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { - return (_buttons[index]._state == S_down); - } else { - return false; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::is_button_known -// Access: Public -// Description: Returns true if the state of the indicated button is -// known, or false if we have never heard anything about -// this particular button. -//////////////////////////////////////////////////////////////////// -INLINE bool ClientButtonDevice:: -is_button_known(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { - return _buttons[index]._state != S_unknown; - } else { - return false; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::get_button_events -// Access: Public -// Description: Returns the list of recently-generated ButtonEvents. -// This must be periodically cleared, or the buttons -// will accumulate. -//////////////////////////////////////////////////////////////////// -INLINE ButtonEventList *ClientButtonDevice:: -get_button_events() const { - return _button_events; -} diff --git a/panda/src/device/clientButtonDevice.cxx b/panda/src/device/clientButtonDevice.cxx index e45561ac5a..02980c55b1 100644 --- a/panda/src/device/clientButtonDevice.cxx +++ b/panda/src/device/clientButtonDevice.cxx @@ -28,47 +28,6 @@ ClientButtonDevice:: ClientButtonDevice(ClientBase *client, const string &device_name): ClientDevice(client, get_class_type(), device_name) { - _button_events = new ButtonEventList(); -} - - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::set_button_state -// Access: Public -// Description: Sets the state of the indicated button index, where -// true indicates down, and false indicates up. This -// may generate a ButtonEvent if the button has an -// associated ButtonHandle. The caller should ensure -// that acquire() is in effect while this call is made. -//////////////////////////////////////////////////////////////////// -void ClientButtonDevice:: -set_button_state(int index, bool down) { - ensure_button_index(index); - nassertv(index >= 0 && index < (int)_buttons.size()); - _buttons[index]._state = down ? S_down : S_up; - - ButtonHandle handle = _buttons[index]._handle; - if (handle != ButtonHandle::none()) { - _button_events->add_event(ButtonEvent(handle, down ? ButtonEvent::T_down : ButtonEvent::T_up)); - } -} - - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::ensure_button_index -// Access: Private -// Description: Guarantees that there is a slot in the array for the -// indicated index number, by filling the array up to -// that index if necessary. -//////////////////////////////////////////////////////////////////// -void ClientButtonDevice:: -ensure_button_index(int index) { - nassertv(index >= 0); - - _buttons.reserve(index + 1); - while ((int)_buttons.size() <= index) { - _buttons.push_back(ButtonState()); - } } //////////////////////////////////////////////////////////////////// @@ -78,7 +37,7 @@ ensure_button_index(int index) { //////////////////////////////////////////////////////////////////// void ClientButtonDevice:: output(ostream &out) const { - out << get_type() << " " << get_device_name() << " ("; + out << get_type() << " " << get_name() << " ("; output_buttons(out); out << ")"; } @@ -90,74 +49,6 @@ output(ostream &out) const { //////////////////////////////////////////////////////////////////// void ClientButtonDevice:: write(ostream &out, int indent_level) const { - indent(out, indent_level) << get_type() << " " << get_device_name() << ":\n"; + indent(out, indent_level) << get_type() << " " << get_name() << ":\n"; write_buttons(out, indent_level + 2); } - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::output_buttons -// Access: Public -// Description: Writes a one-line string of all of the current button -// states. -//////////////////////////////////////////////////////////////////// -void ClientButtonDevice:: -output_buttons(ostream &out) const { - bool any_buttons = false; - Buttons::const_iterator bi; - for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { - const ButtonState &state = (*bi); - if (state._state != S_unknown) { - if (any_buttons) { - out << ", "; - } - any_buttons = true; - out << (int)(bi - _buttons.begin()) << "="; - if (state._state == S_up) { - out << "up"; - } else { - out << "down"; - } - } - } - - if (!any_buttons) { - out << "no known buttons"; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::write_buttons -// Access: Public -// Description: Writes a multi-line description of the current button -// states. -//////////////////////////////////////////////////////////////////// -void ClientButtonDevice:: -write_buttons(ostream &out, int indent_level) const { - bool any_buttons = false; - Buttons::const_iterator bi; - for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { - const ButtonState &state = (*bi); - if (state._state != S_unknown) { - any_buttons = true; - - indent(out, indent_level) - << (int)(bi - _buttons.begin()) << ". "; - - if (state._handle != ButtonHandle::none()) { - out << "(" << state._handle << ") "; - } - - if (state._state == S_up) { - out << "up"; - } else { - out << "down"; - } - out << "\n"; - } - } - - if (!any_buttons) { - indent(out, indent_level) - << "(no known buttons)\n"; - } -} diff --git a/panda/src/device/clientButtonDevice.h b/panda/src/device/clientButtonDevice.h index b9643d26c2..3deee23cbe 100644 --- a/panda/src/device/clientButtonDevice.h +++ b/panda/src/device/clientButtonDevice.h @@ -39,46 +39,9 @@ protected: ClientButtonDevice(ClientBase *client, const string &device_name); public: - INLINE int get_num_buttons() const; - - INLINE void set_button_map(int index, ButtonHandle button); - INLINE ButtonHandle get_button_map(int index) const; - - void set_button_state(int index, bool down); - INLINE bool get_button_state(int index) const; - INLINE bool is_button_known(int index) const; - - INLINE ButtonEventList *get_button_events() const; - virtual void output(ostream &out) const; virtual void write(ostream &out, int indent_level = 0) const; - void output_buttons(ostream &out) const; - void write_buttons(ostream &out, int indent_level) const; - -private: - void ensure_button_index(int index); - -protected: - enum State { - S_unknown, - S_up, - S_down - }; - - class ButtonState { - public: - INLINE ButtonState(); - - ButtonHandle _handle; - State _state; - }; - - typedef pvector Buttons; - Buttons _buttons; - - PT(ButtonEventList) _button_events; - public: static TypeHandle get_class_type() { return _type_handle; @@ -95,7 +58,6 @@ public: private: static TypeHandle _type_handle; - friend class ButtonState; }; #include "clientButtonDevice.I" diff --git a/panda/src/device/clientDevice.I b/panda/src/device/clientDevice.I index efcb92e050..d867978684 100644 --- a/panda/src/device/clientDevice.I +++ b/panda/src/device/clientDevice.I @@ -24,17 +24,6 @@ get_client() const { return _client; } -//////////////////////////////////////////////////////////////////// -// Function: ClientDevice::is_connected -// Access: Public -// Description: Returns true if the device is still connected to its -// ClientBase, false otherwise. -//////////////////////////////////////////////////////////////////// -INLINE bool ClientDevice:: -is_connected() const { - return _is_connected; -} - //////////////////////////////////////////////////////////////////// // Function: ClientDevice::get_device_type // Access: Public @@ -50,45 +39,3 @@ INLINE TypeHandle ClientDevice:: get_device_type() const { return _device_type; } - -//////////////////////////////////////////////////////////////////// -// Function: ClientDevice::get_device_name -// Access: Public -// Description: Returns the device name reported to the ClientBase. -// This has some implementation-defined meaning to -// identify particular devices. -//////////////////////////////////////////////////////////////////// -INLINE const string &ClientDevice:: -get_device_name() const { - return _device_name; -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientDevice::acquire -// Access: Public -// Description: Grabs the mutex associated with this particular -// device. The device will not update asynchronously -// while the mutex is held, allowing the user to copy -// the data out without fear of getting a partial update -// during the copy. -//////////////////////////////////////////////////////////////////// -INLINE void ClientDevice:: -acquire() { -#ifdef OLD_HAVE_IPC - _lock.acquire(); -#endif -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientDevice::unlock -// Access: Public -// Description: Releases the mutex associated with this particular -// device. This should be called after all the data has -// been successfully copied out. See acquire(). -//////////////////////////////////////////////////////////////////// -INLINE void ClientDevice:: -unlock() { -#ifdef OLD_HAVE_IPC - _lock.unlock(); -#endif -} diff --git a/panda/src/device/clientDevice.cxx b/panda/src/device/clientDevice.cxx index d7f7aa90f4..c0ee85d266 100644 --- a/panda/src/device/clientDevice.cxx +++ b/panda/src/device/clientDevice.cxx @@ -27,15 +27,14 @@ TypeHandle ClientDevice::_type_handle; //////////////////////////////////////////////////////////////////// ClientDevice:: ClientDevice(ClientBase *client, TypeHandle device_type, - const string &device_name) : + const string &device_name, int device_flags) : + InputDevice(device_name, DC_unknown, device_flags), _client(client), - _device_type(device_type), - _device_name(device_name) + _device_type(device_type) { // We have to explicitly ref the client pointer, since we can't use // a PT(ClientBase) for circular include reasons. _client->ref(); - _is_connected = false; } //////////////////////////////////////////////////////////////////// @@ -49,7 +48,7 @@ ClientDevice(ClientBase *client, TypeHandle device_type, //////////////////////////////////////////////////////////////////// ClientDevice:: ~ClientDevice() { - nassertv(!_is_connected); + nassertv(!is_connected()); // And now we explicitly unref the client pointer. unref_delete(_client); @@ -72,19 +71,17 @@ ClientDevice:: //////////////////////////////////////////////////////////////////// void ClientDevice:: disconnect() { - if (_is_connected) { - acquire(); + if (is_connected()) { bool disconnected = - _client->disconnect_device(_device_type, _device_name, this); - _is_connected = false; - unlock(); + _client->disconnect_device(_device_type, get_name(), this); + set_connected(false); nassertv(disconnected); } } //////////////////////////////////////////////////////////////////// -// Function: ClientDevice::poll -// Access: Public +// Function: ClientDevice::do_poll +// Access: Public, Virtual, Final // Description: Causes the connected ClientBase to poll all of its // clients, if necessary. This will be a no-op if the // client is running in forked mode, or if it has @@ -94,7 +91,7 @@ disconnect() { // data in this ClientDevice to ensure that it is fresh. //////////////////////////////////////////////////////////////////// void ClientDevice:: -poll() { +do_poll() { _client->poll(); } @@ -105,7 +102,7 @@ poll() { //////////////////////////////////////////////////////////////////// void ClientDevice:: output(ostream &out) const { - out << get_type() << " " << get_device_name(); + out << get_type() << " " << get_name(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/device/clientDevice.h b/panda/src/device/clientDevice.h index b0dbb116e3..3e040bc742 100644 --- a/panda/src/device/clientDevice.h +++ b/panda/src/device/clientDevice.h @@ -16,12 +16,7 @@ #define CLIENTDEVICE_H #include "pandabase.h" - -#include "typedReferenceCount.h" - -#ifdef OLD_HAVE_IPC -#include -#endif +#include "inputDevice.h" class ClientBase; @@ -32,24 +27,20 @@ class ClientBase; // This is an abstract interface; the actual // implementations are in ClientTrackerDevice, etc. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA_DEVICE ClientDevice : public TypedReferenceCount { +class EXPCL_PANDA_DEVICE ClientDevice : public InputDevice { protected: ClientDevice(ClientBase *client, TypeHandle device_type, - const string &device_name); + const string &device_name, int device_flags=0); public: virtual ~ClientDevice(); INLINE ClientBase *get_client() const; INLINE TypeHandle get_device_type() const; - INLINE const string &get_device_name() const; - INLINE bool is_connected() const; void disconnect(); - void poll(); - INLINE void acquire(); - INLINE void unlock(); + virtual void do_poll() FINAL; virtual void output(ostream &out) const; virtual void write(ostream &out, int indent_level = 0) const; @@ -57,21 +48,15 @@ public: private: ClientBase *_client; TypeHandle _device_type; - string _device_name; - bool _is_connected; - -#ifdef OLD_HAVE_IPC - mutex _lock; -#endif public: static TypeHandle get_class_type() { return _type_handle; } static void init_type() { - TypedReferenceCount::init_type(); + InputDevice::init_type(); register_type(_type_handle, "ClientDevice", - TypedReferenceCount::get_class_type()); + InputDevice::get_class_type()); } virtual TypeHandle get_type() const { return get_class_type(); diff --git a/panda/src/device/clientTrackerDevice.I b/panda/src/device/clientTrackerDevice.I index 938ac26922..1b9047c10f 100644 --- a/panda/src/device/clientTrackerDevice.I +++ b/panda/src/device/clientTrackerDevice.I @@ -20,18 +20,6 @@ //////////////////////////////////////////////////////////////////// INLINE ClientTrackerDevice:: ClientTrackerDevice(ClientBase *client, const string &device_name): - ClientDevice(client, get_class_type(), device_name) + ClientDevice(client, get_class_type(), device_name, IDF_has_tracker) { } - -//////////////////////////////////////////////////////////////////// -// Function: ClientTrackerDevice::get_data -// Access: Public -// Description: Returns the TrackerData that this device is -// reporting. -//////////////////////////////////////////////////////////////////// -INLINE const TrackerData &ClientTrackerDevice:: -get_data() const { - return _data; -} - diff --git a/panda/src/device/clientTrackerDevice.h b/panda/src/device/clientTrackerDevice.h index 8fbfd00c55..04123ca0f4 100644 --- a/panda/src/device/clientTrackerDevice.h +++ b/panda/src/device/clientTrackerDevice.h @@ -30,12 +30,6 @@ class EXPCL_PANDA_DEVICE ClientTrackerDevice : public ClientDevice { protected: INLINE ClientTrackerDevice(ClientBase *client, const string &device_name); -public: - INLINE const TrackerData &get_data() const; - -protected: - TrackerData _data; - public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/device/config_device.cxx b/panda/src/device/config_device.cxx index a86986ed05..84e290be91 100644 --- a/panda/src/device/config_device.cxx +++ b/panda/src/device/config_device.cxx @@ -23,7 +23,9 @@ #include "clientDialDevice.h" #include "clientTrackerDevice.h" #include "dialNode.h" -#include "mouseAndKeyboard.h" +#include "evdevInputDevice.h" +#include "inputDevice.h" +#include "linuxJoystickDevice.h" #include "trackerNode.h" #include "virtualMouse.h" @@ -64,7 +66,12 @@ init_libdevice() { ClientDialDevice::init_type(); ClientTrackerDevice::init_type(); DialNode::init_type(); - MouseAndKeyboard::init_type(); + InputDevice::init_type(); TrackerNode::init_type(); VirtualMouse::init_type(); + +#ifdef PHAVE_LINUX_INPUT_H + EvdevInputDevice::init_type(); + LinuxJoystickDevice::init_type(); +#endif } diff --git a/panda/src/device/dialNode.I b/panda/src/device/dialNode.I index bd04635e46..ab101901b7 100644 --- a/panda/src/device/dialNode.I +++ b/panda/src/device/dialNode.I @@ -33,10 +33,7 @@ is_valid() const { //////////////////////////////////////////////////////////////////// INLINE int DialNode:: get_num_dials() const { - _dial->acquire(); - int result = _dial->get_num_dials(); - _dial->unlock(); - return result; + return _dial->get_num_dials(); } //////////////////////////////////////////////////////////////////// @@ -49,10 +46,7 @@ get_num_dials() const { //////////////////////////////////////////////////////////////////// INLINE double DialNode:: read_dial(int index) { - _dial->acquire(); - double result = _dial->read_dial(index); - _dial->unlock(); - return result; + return _dial->read_dial(index); } //////////////////////////////////////////////////////////////////// @@ -64,8 +58,5 @@ read_dial(int index) { //////////////////////////////////////////////////////////////////// INLINE bool DialNode:: is_dial_known(int index) const { - _dial->acquire(); - bool result = _dial->is_dial_known(index); - _dial->unlock(); - return result; + return _dial->is_dial_known(index); } diff --git a/panda/src/device/evdevInputDevice.I b/panda/src/device/evdevInputDevice.I new file mode 100644 index 0000000000..9e702ad1d8 --- /dev/null +++ b/panda/src/device/evdevInputDevice.I @@ -0,0 +1,14 @@ +// Filename: evdevInputDevice.I +// Created by: rdb (24Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx new file mode 100644 index 0000000000..e03934d658 --- /dev/null +++ b/panda/src/device/evdevInputDevice.cxx @@ -0,0 +1,424 @@ +// Filename: evdevInputDevice.cxx +// Created by: rdb (24Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "evdevInputDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +#include "gamepadButton.h" +#include "keyboardButton.h" +#include "mouseButton.h" + +#include +#include + +#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7))) + +TypeHandle EvdevInputDevice::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::Constructor +// Access: Published +// Description: Creates a new device using the Linux joystick +// device using the given file descriptor. It will +// be closed when this object destructs. +//////////////////////////////////////////////////////////////////// +EvdevInputDevice:: +EvdevInputDevice(int fd) : + _fd(fd) { + init_device(); +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::Constructor +// Access: Published +// Description: Creates a new device using the Linux joystick +// device using the given device filename. +//////////////////////////////////////////////////////////////////// +EvdevInputDevice:: +EvdevInputDevice(const string &fn) { + _fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK); + + if (_fd >= 0) { + init_device(); + } else { + _is_connected = false; + device_cat.error() + << "Opening raw input device: " << strerror(errno) << " " << fn << "\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +EvdevInputDevice:: +~EvdevInputDevice() { + if (_fd != -1) { + close(_fd); + _fd = -1; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::do_poll +// Access: Private, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void EvdevInputDevice:: +do_poll() { + if (_fd != -1) { + while (process_events()) {} + } +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::init_device +// Access: Private +// Description: Reads basic properties from the device. +//////////////////////////////////////////////////////////////////// +bool EvdevInputDevice:: +init_device() { + uint8_t evtypes[EV_MAX/8 + 1]; + char name[256]; + char uniq[256]; + if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || + ioctl(_fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0 || + ioctl(_fd, EVIOCGBIT(0, EV_MAX), &evtypes) < 0) { + close(_fd); + _fd = -1; + _is_connected = false; + device_cat.error() << "Opening raw input device: ioctl failed\n"; + return false; + } + + for (char *p=name; *p; p++) { + if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { + *p = '_'; + } + } + for (char *p=uniq; *p; p++) { + if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { + *p = '_'; + } + } + + _name = ((string)name) + "." + uniq; + + if (test_bit(EV_REL, evtypes)) { + _flags |= IDF_has_pointer; + } + + if (test_bit(EV_FF, evtypes)) { + _flags |= IDF_has_vibration; + } + + _is_connected = true; + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::process_events +// Access: Private +// Description: Reads a number of events from the device. +//////////////////////////////////////////////////////////////////// +bool EvdevInputDevice:: +process_events() { + // Read 8 events at a time. + struct input_event events[8]; + + int n_read = read(_fd, events, sizeof(events)); + if (n_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available for now. + + } else if (errno == ENODEV || errno == EINVAL) { + // The device ceased to exist, so we better close it. + close(_fd); + _fd = -1; + _is_connected = false; + errno = 0; + + } else { + device_cat.error() << "read: " << strerror(errno) << "\n"; + } + return false; + } + + if (n_read == 0) { + return false; + } + + n_read /= sizeof(struct input_event); + + int x = _pointer_data.get_x(); + int y = _pointer_data.get_y(); + double time = ClockObject::get_global_clock()->get_frame_time(); + ButtonHandle button; + + for (int i = 0; i < n_read; ++i) { + switch (events[i].type) { + case EV_SYN: + break; + + case EV_REL: + if (events[i].code == REL_X) x += events[i].value; + if (events[i].code == REL_Y) y += events[i].value; + break; + + case EV_ABS: + if (events[i].code == ABS_X) x = events[i].value; + if (events[i].code == ABS_Y) y = events[i].value; + break; + + case EV_KEY: + button = map_button(events[i].code); + _button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time)); + break; + + default: + //cerr << "event " << events[i].type << " - " << events[i].code << " - " << events[i].value << "\n"; + break; + } + } + + set_pointer(true, x, y, time); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::map_button +// Access: Public, Static +// Description: Maps an evdev code to a ButtonHandle. +//////////////////////////////////////////////////////////////////// +ButtonHandle EvdevInputDevice:: +map_button(int code) { + if (code >= 0 && code < 0x80) { + // See linux/input.h for the source of this mapping. + static const ButtonHandle keyboard_map[] = { + ButtonHandle::none(), + KeyboardButton::escape(), + KeyboardButton::ascii_key('1'), + KeyboardButton::ascii_key('2'), + KeyboardButton::ascii_key('3'), + KeyboardButton::ascii_key('4'), + KeyboardButton::ascii_key('5'), + KeyboardButton::ascii_key('6'), + KeyboardButton::ascii_key('7'), + KeyboardButton::ascii_key('8'), + KeyboardButton::ascii_key('9'), + KeyboardButton::ascii_key('0'), + KeyboardButton::ascii_key('-'), + KeyboardButton::ascii_key('='), + KeyboardButton::backspace(), + KeyboardButton::tab(), + KeyboardButton::ascii_key('q'), + KeyboardButton::ascii_key('w'), + KeyboardButton::ascii_key('e'), + KeyboardButton::ascii_key('r'), + KeyboardButton::ascii_key('t'), + KeyboardButton::ascii_key('y'), + KeyboardButton::ascii_key('u'), + KeyboardButton::ascii_key('i'), + KeyboardButton::ascii_key('o'), + KeyboardButton::ascii_key('p'), + KeyboardButton::ascii_key('['), + KeyboardButton::ascii_key(']'), + KeyboardButton::enter(), + KeyboardButton::lcontrol(), + KeyboardButton::ascii_key('a'), + KeyboardButton::ascii_key('s'), + KeyboardButton::ascii_key('d'), + KeyboardButton::ascii_key('f'), + KeyboardButton::ascii_key('g'), + KeyboardButton::ascii_key('h'), + KeyboardButton::ascii_key('j'), + KeyboardButton::ascii_key('k'), + KeyboardButton::ascii_key('l'), + KeyboardButton::ascii_key(';'), + KeyboardButton::ascii_key('\''), + KeyboardButton::ascii_key('`'), + KeyboardButton::lshift(), + KeyboardButton::ascii_key('\\'), + KeyboardButton::ascii_key('z'), + KeyboardButton::ascii_key('x'), + KeyboardButton::ascii_key('c'), + KeyboardButton::ascii_key('v'), + KeyboardButton::ascii_key('b'), + KeyboardButton::ascii_key('n'), + KeyboardButton::ascii_key('m'), + KeyboardButton::ascii_key(','), + KeyboardButton::ascii_key('.'), + KeyboardButton::ascii_key('/'), + KeyboardButton::rshift(), + KeyboardButton::ascii_key('*'), + KeyboardButton::lalt(), + KeyboardButton::space(), + KeyboardButton::caps_lock(), + KeyboardButton::f1(), + KeyboardButton::f2(), + KeyboardButton::f3(), + KeyboardButton::f4(), + KeyboardButton::f5(), + KeyboardButton::f6(), + KeyboardButton::f7(), + KeyboardButton::f8(), + KeyboardButton::f9(), + KeyboardButton::f10(), + KeyboardButton::num_lock(), + KeyboardButton::scroll_lock(), + KeyboardButton::ascii_key('7'), + KeyboardButton::ascii_key('8'), + KeyboardButton::ascii_key('9'), + KeyboardButton::ascii_key('-'), + KeyboardButton::ascii_key('4'), + KeyboardButton::ascii_key('5'), + KeyboardButton::ascii_key('6'), + KeyboardButton::ascii_key('+'), + KeyboardButton::ascii_key('1'), + KeyboardButton::ascii_key('2'), + KeyboardButton::ascii_key('3'), + KeyboardButton::ascii_key('0'), + KeyboardButton::ascii_key('.'), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + KeyboardButton::f11(), + KeyboardButton::f12(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + KeyboardButton::enter(), + KeyboardButton::rcontrol(), + KeyboardButton::ascii_key('/'), + KeyboardButton::print_screen(), + KeyboardButton::ralt(), + ButtonHandle::none(), + KeyboardButton::home(), + KeyboardButton::up(), + KeyboardButton::page_up(), + KeyboardButton::left(), + KeyboardButton::right(), + KeyboardButton::end(), + KeyboardButton::down(), + KeyboardButton::page_down(), + KeyboardButton::insert(), + KeyboardButton::del(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + KeyboardButton::pause(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + KeyboardButton::lmeta(), + KeyboardButton::rmeta(), + KeyboardButton::menu(), + }; + return keyboard_map[code]; + + } else if (code < 0x100) { + return ButtonHandle::none(); + + } else if ((code & 0xfff0) == BTN_MOUSE) { + // The number for these is reversed in Panda. + if (code == BTN_RIGHT) { + return MouseButton::three(); + } else if (code == BTN_MIDDLE) { + return MouseButton::two(); + } else { + return MouseButton::button(code - BTN_MOUSE); + } + } + + switch (code) { + case BTN_A: + return GamepadButton::action_a(); + + case BTN_B: + return GamepadButton::action_b(); + + case BTN_C: + return GamepadButton::action_c(); + + case BTN_X: + return GamepadButton::action_x(); + + case BTN_Y: + return GamepadButton::action_y(); + + case BTN_Z: + return GamepadButton::action_z(); + + case BTN_TL: + return GamepadButton::lshoulder(); + + case BTN_TR: + return GamepadButton::rshoulder(); + + case BTN_TL2: + return GamepadButton::ltrigger(); + + case BTN_TR2: + return GamepadButton::rtrigger(); + + case BTN_SELECT: + return GamepadButton::back(); + + case BTN_START: + return GamepadButton::start(); + + case BTN_MODE: + return GamepadButton::guide(); + + case BTN_THUMBL: + return GamepadButton::lstick(); + + case BTN_THUMBR: + return GamepadButton::rstick(); + + case BTN_TRIGGER_HAPPY1: + return GamepadButton::dpad_left(); + + case BTN_TRIGGER_HAPPY2: + return GamepadButton::dpad_right(); + + case BTN_TRIGGER_HAPPY3: + return GamepadButton::dpad_up(); + + case BTN_TRIGGER_HAPPY4: + return GamepadButton::dpad_down(); + + default: + return ButtonHandle::none(); + } +} + +#endif diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h new file mode 100644 index 0000000000..d3d4203625 --- /dev/null +++ b/panda/src/device/evdevInputDevice.h @@ -0,0 +1,63 @@ +// Filename: evdevInputDevice.h +// Created by: rdb (24Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef EVDEVINPUTDEVICE_H +#define EVDEVINPUTDEVICE_H + +#include "inputDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +//////////////////////////////////////////////////////////////////// +// Class : EvdevInputDevice +// Description : This is a type of device that uses the Linux +// /dev/input/event# API to read data from a raw mouse. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { +PUBLISHED: + EvdevInputDevice(int fd); + EvdevInputDevice(const string &device); + virtual ~EvdevInputDevice(); + +private: + virtual void do_poll(); + + bool init_device(); + bool process_events(); + +private: + int _fd; + +public: + static ButtonHandle map_button(int code); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + InputDevice::init_type(); + register_type(_type_handle, "EvdevInputDevice", + InputDevice::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "evdevInputDevice.I" + +#endif // PHAVE_LINUX_INPUT_H + +#endif // EVDEVINPUTDEVICE_H diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I new file mode 100644 index 0000000000..99962b7750 --- /dev/null +++ b/panda/src/device/inputDevice.I @@ -0,0 +1,445 @@ +// Filename: inputDevice.I +// Created by: drose (24May00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Default Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE InputDevice:: +InputDevice() : + _flags(0), + _device_class(DC_unknown), + _is_connected(false), + _event_sequence(0), + _enable_pointer_events(false), + _battery_level(-1), + _max_battery_level(-1) +{ + _button_events = new ButtonEventList; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_name +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE string InputDevice:: +get_name() const { + LightMutexHolder holder(_lock); + return _name; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::is_connected +// Access: Public +// Description: Returns true if the device is still connected and +// able to receive data, false otherwise. May return +// false positives. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +is_connected() const { + LightMutexHolder holder(_lock); + return _is_connected; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_device_class +// Access: Public +// Description: Returns an identification of the general type of +// device. If this could not be determined, returns +// DC_unknown. +//////////////////////////////////////////////////////////////////// +INLINE InputDevice::DeviceClass InputDevice:: +get_device_class() const { + LightMutexHolder holder(_lock); + return _device_class; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_pointer +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +has_pointer() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_pointer) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_keyboard +// Access: Public +// Description: Returns true if the device has a physical keyboard +// designed for text entry. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +has_keyboard() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_keyboard) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_tracker +// Access: Public +// Description: Returns true if the device features a tracker that +// can track position and/or orientation in 3D space. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +has_tracker() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_tracker) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_battery +// Access: Public +// Description: Returns true if the device may be able to provide +// information about its battery life. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +has_battery() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_battery) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_axis +// Access: Public +// Description: Returns true if the device has the given axis. +//////////////////////////////////////////////////////////////////// +/* +INLINE bool InputDevice:: +has_axis(Axis axis) const { + LightMutexHolder holder(_lock); + return ((_has_axes & (1 << axis)) != 0); +}*/ + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_pointer +// Access: Public +// Description: Returns the PointerData associated with the input +// device's pointer. This only makes sense if +// has_pointer() also returns true. +//////////////////////////////////////////////////////////////////// +INLINE PointerData InputDevice:: +get_pointer() const { + LightMutexHolder holder(_lock); + return _pointer_data; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_tracker +// Access: Public +// Description: Returns the TrackerData associated with the input +// device's tracker. This only makes sense if +// has_tracker() also returns true. +//////////////////////////////////////////////////////////////////// +INLINE TrackerData InputDevice:: +get_tracker() const { + LightMutexHolder holder(_lock); + return _tracker_data; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_battery_level +// Access: Public +// Description: Returns a rough indication of the battery level, +// ranging from 0 (completely empty battery) to the +// number reported by get_max_battery_level(), which +// represents a full battery. +// If this information is not known, returns -1. +//////////////////////////////////////////////////////////////////// +INLINE short InputDevice:: +get_battery_level() const { + LightMutexHolder holder(_lock); + return _battery_level; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_max_battery_level +// Access: Public +// Description: Returns the maximum value that may be reported by +// get_battery_level(), representing a full battery. +// Returns -1 if no battery information is known. +//////////////////////////////////////////////////////////////////// +INLINE short InputDevice:: +get_max_battery_level() const { + LightMutexHolder holder(_lock); + return _max_battery_level; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_num_buttons +// Access: Public +// Description: Returns the number of buttons known to the +// ClientButtonDevice. This includes those buttons +// whose state has been seen, as well as buttons that +// have been associated with a ButtonHandle even if +// their state is unknown. This number may change as +// more buttons are discovered. +//////////////////////////////////////////////////////////////////// +INLINE int InputDevice:: +get_num_buttons() const { + LightMutexHolder holder(_lock); + return _buttons.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_button_map +// Access: Public +// Description: Associates the indicated ButtonHandle with the button +// of the indicated index number. When the given button +// index changes state, a corresponding ButtonEvent will +// be generated with the given ButtonHandle. Pass +// ButtonHandle::none() to turn off any association. +// +// It is not necessary to call this if you simply want +// to query the state of the various buttons by index +// number; this is only necessary in order to generate +// ButtonEvents when the buttons change state. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +set_button_map(int index, ButtonHandle button) { + LightMutexHolder holder(_lock); + nassertv(index >= 0); + if (index >= (int)_buttons.size()) { + _buttons.resize(index + 1, ButtonState()); + } + + _buttons[index]._handle = button; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_button_map +// Access: Public +// Description: Returns the ButtonHandle that was previously +// associated with the given index number by +// a call to set_button_map(), or ButtonHandle::none() +// if no button was associated. +//////////////////////////////////////////////////////////////////// +INLINE ButtonHandle InputDevice:: +get_button_map(int index) const { + if (index >= 0 && index < (int)_buttons.size()) { + return _buttons[index]._handle; + } else { + return ButtonHandle::none(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_button_state +// Access: Public +// Description: Returns true if the indicated button (identified by +// its index number) is currently known to be down, or +// false if it is up or unknown. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +get_button_state(int index) const { + if (index >= 0 && index < (int)_buttons.size()) { + return (_buttons[index]._state == S_down); + } else { + return false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::is_button_known +// Access: Public +// Description: Returns true if the state of the indicated button is +// known, or false if we have never heard anything about +// this particular button. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +is_button_known(int index) const { + if (index >= 0 && index < (int)_buttons.size()) { + return _buttons[index]._state != S_unknown; + } else { + return false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_num_controls +// Access: Public +// Description: Returns the number of analog controls known to the +// InputDevice. This number may change as +// more controls are discovered. +//////////////////////////////////////////////////////////////////// +INLINE int InputDevice:: +get_num_controls() const { + return _controls.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_control_map +// Access: Public +// Description: Associates the indicated ControlAxis with the +// control of the indicated index number. Pass +// C_none to turn off any association. +// +// It is not necessary to call this if you simply want +// to query the state of the various controls by index +// number. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +set_control_map(int index, ControlAxis axis) { + LightMutexHolder holder(_lock); + nassertv(index >= 0); + if (index >= (int)_controls.size()) { + _controls.resize(index + 1, AnalogState()); + } + + _controls[index]._axis = axis; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_control_map +// Access: Public +// Description: Returns the ControlAxis that was previously +// associated with the given index number by +// a call to set_control_map(), or C_none +// if no control was associated. +//////////////////////////////////////////////////////////////////// +INLINE InputDevice::ControlAxis InputDevice:: +get_control_map(int index) const { + if (index >= 0 && index < (int)_controls.size()) { + return _controls[index]._axis; + } else { + return C_none; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_control_state +// Access: Public +// Description: Returns the current position of indicated analog +// control (identified by its index number), or 0.0 if +// the control is unknown. The normal range of a single +// control is -1.0 to 1.0. +//////////////////////////////////////////////////////////////////// +INLINE double InputDevice:: +get_control_state(int index) const { + if (index >= 0 && index < (int)_controls.size()) { + return _controls[index]._state; + } else { + return 0.0; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::is_control_known +// Access: Public +// Description: Returns true if the state of the indicated analog +// control is known, or false if we have never heard +// anything about this particular control. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +is_control_known(int index) const { + if (index >= 0 && index < (int)_controls.size()) { + return _controls[index]._known; + } else { + return false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::enable_pointer_events +// Access: Public +// Description: Enables the generation of mouse-movement events. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +enable_pointer_events() { + LightMutexHolder holder(_lock); + _enable_pointer_events = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::disable_pointer_events +// Access: Public +// Description: Disables the generation of mouse-movement events. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +disable_pointer_events() { + LightMutexHolder holder(_lock); + _enable_pointer_events = false; + _pointer_events.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_connected +// Access: Protected +// Description: Called to indicate that the device has been +// disconnected or connected from its host. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +set_connected(bool connected) { + LightMutexHolder holder(_lock); + _is_connected = connected; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::operator == +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +operator == (const InputDevice &) const { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::operator != +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +operator != (const InputDevice &) const { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::operator < +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +operator < (const InputDevice &) const { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::ButtonState::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE InputDevice::ButtonState:: +ButtonState() : + _handle(ButtonHandle::none()), + _state(S_unknown) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::AnalogState::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE InputDevice::AnalogState:: +AnalogState() : + _axis(C_none), + _state(0.0), + _known(false) +{ +} diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx new file mode 100644 index 0000000000..25477ca913 --- /dev/null +++ b/panda/src/device/inputDevice.cxx @@ -0,0 +1,517 @@ +// Filename: inputDevice.cxx +// Created by: drose (24May00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "inputDevice.h" + +TypeHandle InputDevice::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Constructor +// Access: Private +// Description: Defines a new InputDevice for the window. Most +// windows will have exactly one InputDevice: a +// keyboard/mouse pair. Some may also add joystick +// data, or additional mice or something. +// +// This private constructor is only used internally by +// the named constructors, below. +//////////////////////////////////////////////////////////////////// +InputDevice:: +InputDevice(const string &name, DeviceClass dev_class, int flags) : + _name(name), + _flags(flags), + _device_class(dev_class), + _is_connected(true), + _event_sequence(0), + _enable_pointer_events(false), + _battery_level(-1), + _max_battery_level(-1) +{ + _button_events = new ButtonEventList; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +InputDevice:: +InputDevice(const InputDevice ©) { + *this = copy; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Copy Assignment Operator +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void InputDevice:: +operator = (const InputDevice ©) { + LightMutexHolder holder(_lock); + LightMutexHolder holder1(copy._lock); + _name = copy._name; + _flags = copy._flags; + _is_connected = copy._is_connected; + _event_sequence = copy._event_sequence; + _enable_pointer_events = copy._enable_pointer_events; + _pointer_data = copy._pointer_data; + _button_events = copy._button_events; + _pointer_events = copy._pointer_events; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +InputDevice:: +~InputDevice() { +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::poll +// Access: Public, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +poll() { + LightMutexHolder holder(_lock); + do_poll(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_button_event +// Access: Public +// Description: Returns true if this device has a pending button +// event (a mouse button or keyboard button down/up), +// false otherwise. If this returns true, the +// particular event may be extracted via +// get_button_event(). +//////////////////////////////////////////////////////////////////// +bool InputDevice:: +has_button_event() const { + LightMutexHolder holder(_lock); + return !_button_events.is_null() && _button_events->get_num_events() > 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_button_events +// Access: Public +// Description: Returns the list of recently-generated ButtonEvents. +// The list is also cleared. +//////////////////////////////////////////////////////////////////// +PT(ButtonEventList) InputDevice:: +get_button_events() { + LightMutexHolder holder(_lock); + PT(ButtonEventList) result = new ButtonEventList; + swap(_button_events, result); + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_pointer_event +// Access: Public +// Description: Returns true if this device has a pending pointer +// event (a mouse movement), or false otherwise. If +// this returns true, the particular event may be +// extracted via get_pointer_event(). +//////////////////////////////////////////////////////////////////// +bool InputDevice:: +has_pointer_event() const { + LightMutexHolder holder(_lock); + return (_pointer_events != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_pointer_events +// Access: Public +// Description: Returns a PointerEventList containing all the recent +// pointer events. Clears the list. +//////////////////////////////////////////////////////////////////// +PT(PointerEventList) InputDevice:: +get_pointer_events() { + LightMutexHolder holder(_lock); + PT(PointerEventList) result = _pointer_events; + _pointer_events.clear(); + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_pointer +// Access: Protected +// Description: Records that a mouse movement has taken place. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_pointer(bool inwin, double x, double y, double time) { + nassertv(_lock.debug_is_locked()); + _pointer_data._in_window = inwin; + _pointer_data._xpos = x; + _pointer_data._ypos = y; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + if (_pointer_events.is_null()) { + _pointer_events = new PointerEventList(); + } + _pointer_events->add_event(_pointer_data._in_window, + _pointer_data._xpos, + _pointer_data._ypos, + seq, time); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_pointer_out_of_window +// Access: Protected +// Description: Records that the mouse pointer has left the window. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_pointer_out_of_window(double time) { + nassertv(_lock.debug_is_locked()); + _pointer_data._in_window = false; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + if (_pointer_events.is_null()) { + _pointer_events = new PointerEventList(); + } + _pointer_events->add_event(_pointer_data._in_window, + _pointer_data._xpos, + _pointer_data._ypos, + seq, time); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_button_state +// Access: Protected +// Description: Sets the state of the indicated button index, where +// true indicates down, and false indicates up. This +// may generate a ButtonEvent if the button has an +// associated ButtonHandle. The caller should ensure +// that acquire() is in effect while this call is made. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_button_state(int index, bool down) { + nassertv(_lock.debug_is_locked()); + nassertv(index >= 0); + if (index >= (int)_buttons.size()) { + _buttons.resize(index + 1, ButtonState()); + } + + _buttons[index]._state = down ? S_down : S_up; + + ButtonHandle handle = _buttons[index]._handle; + if (handle != ButtonHandle::none()) { + _button_events->add_event(ButtonEvent(handle, down ? ButtonEvent::T_down : ButtonEvent::T_up)); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_control_state +// Access: Protected +// Description: Sets the state of the indicated analog index. The +// caller should ensure that acquire() is in effect while +// this call is made. This should be a number in the +// range -1.0 to 1.0, representing the current position +// of the control within its total range of movement. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_control_state(int index, double state) { + nassertv(_lock.debug_is_locked()); + nassertv(index >= 0); + if (index >= (int)_controls.size()) { + _controls.resize(index + 1, AnalogState()); + } + + _controls[index]._state = state; + _controls[index]._known = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_tracker +// Access: Protected +// Description: Records that a tracker movement has taken place. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_tracker(const LPoint3 &pos, const LOrientation &orient, double time) { + nassertv(_lock.debug_is_locked()); + + _tracker_data.set_pos(pos); + _tracker_data.set_orient(orient); + _tracker_data.set_time(time); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::output +// Access: Public +// Description: Writes a one-line string describing the device. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +output(ostream &out) const { + LightMutexHolder holder(_lock); + + out << _name << " ("; + + if (!_is_connected) { + out << "dis"; + } + + out << "connected)"; + + if (_device_class != DC_unknown) { + out << ", " << _device_class; + } + + if (_buttons.size() > 0) { + out << ", " << _buttons.size() << " buttons"; + } + + if (_controls.size() > 0) { + out << ", " << _controls.size() << " controls"; + } + + if (_flags & IDF_has_pointer) { + out << ", pointer"; + } + if (_flags & IDF_has_keyboard) { + out << ", keyboard"; + } + if (_flags & IDF_has_tracker) { + out << ", tracker"; + } + if (_flags & IDF_has_vibration) { + out << ", vibration"; + } + if (_flags & IDF_has_battery) { + out << ", battery"; + + if (_battery_level > 0 && _max_battery_level > 0) { + out << " ["; + short i = 0; + for (; i < _battery_level - 1; ++i) { + out << '='; + } + out << '/'; + for (; i < _max_battery_level; ++i) { + out << ' '; + } + out << ']'; + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::output_buttons +// Access: Public +// Description: Writes a one-line string of all of the current button +// states. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +output_buttons(ostream &out) const { + LightMutexHolder holder(_lock); + + bool any_buttons = false; + Buttons::const_iterator bi; + for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { + const ButtonState &state = (*bi); + if (state._state != S_unknown) { + if (any_buttons) { + out << ", "; + } + any_buttons = true; + out << (int)(bi - _buttons.begin()) << "="; + if (state._state == S_up) { + out << "up"; + } else { + out << "down"; + } + } + } + + if (!any_buttons) { + out << "no known buttons"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::write_buttons +// Access: Public +// Description: Writes a multi-line description of the current button +// states. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +write_buttons(ostream &out, int indent_level) const { + bool any_buttons = false; + Buttons::const_iterator bi; + for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { + const ButtonState &state = (*bi); + if (state._state != S_unknown) { + any_buttons = true; + + indent(out, indent_level) + << (int)(bi - _buttons.begin()) << ". "; + + if (state._handle != ButtonHandle::none()) { + out << "(" << state._handle << ") "; + } + + if (state._state == S_up) { + out << "up"; + } else { + out << "down"; + } + out << "\n"; + } + } + + if (!any_buttons) { + indent(out, indent_level) + << "(no known buttons)\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::write_controls +// Access: Public +// Description: Writes a multi-line description of the current analog +// control states. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +write_controls(ostream &out, int indent_level) const { + LightMutexHolder holder(_lock); + + bool any_controls = false; + Controls::const_iterator ai; + for (ai = _controls.begin(); ai != _controls.end(); ++ai) { + const AnalogState &state = (*ai); + if (state._known) { + any_controls = true; + + indent(out, indent_level) + << (int)(ai - _controls.begin()) << ". " << state._state << "\n"; + } + } + + if (!any_controls) { + indent(out, indent_level) + << "(no known analog controls)\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::do_poll +// Access: Protected, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +do_poll() { +} + +ostream & +operator << (ostream &out, InputDevice::DeviceClass dc) { + switch (dc) { + case InputDevice::DC_unknown: + out << "unknown"; + break; + + case InputDevice::DC_virtual: + out << "virtual"; + break; + + case InputDevice::DC_keyboard: + out << "keyboard"; + break; + + case InputDevice::DC_mouse: + out << "mouse"; + break; + + case InputDevice::DC_touch: + out << "touch"; + break; + + case InputDevice::DC_gamepad: + out << "gamepad"; + break; + + case InputDevice::DC_flight_stick: + out << "flight_stick"; + break; + + case InputDevice::DC_steering_wheel: + out << "steering_wheel"; + break; + } + return out; +} + +ostream & +operator << (ostream &out, InputDevice::ControlAxis axis) { + switch (axis) { + case InputDevice::C_none: + out << "none"; + break; + + case InputDevice::C_left_x: + out << "left_x"; + break; + + case InputDevice::C_left_y: + out << "left_y"; + break; + + case InputDevice::C_left_trigger: + out << "left_trigger"; + break; + + case InputDevice::C_right_x: + out << "right_x"; + break; + + case InputDevice::C_right_y: + out << "right_y"; + break; + + case InputDevice::C_right_trigger: + out << "right_trigger"; + break; + + case InputDevice::C_x: + out << "x"; + break; + + case InputDevice::C_y: + out << "y"; + break; + + case InputDevice::C_trigger: + out << "trigger"; + break; + + case InputDevice::C_throttle: + out << "throttle"; + break; + } + + return out; +} diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h new file mode 100644 index 0000000000..407e6cdb77 --- /dev/null +++ b/panda/src/device/inputDevice.h @@ -0,0 +1,279 @@ +// Filename: inputDevice.h +// Created by: drose (24May00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef INPUTDEVICE_H +#define INPUTDEVICE_H + +#include "pandabase.h" + +#include "buttonEvent.h" +#include "buttonEventList.h" +#include "pointerEvent.h" +#include "pointerEventList.h" +#include "mouseData.h" +#include "trackerData.h" +#include "clockObject.h" + +#include "pdeque.h" +#include "pvector.h" +#include "lightMutex.h" +#include "lightMutexHolder.h" + +typedef MouseData PointerData; + +//////////////////////////////////////////////////////////////////// +// Class : InputDevice +// Description : This is a structure representing a single input +// device. Input devices may have zero or more +// buttons, pointers, or controls associated with them, +// and optionally a tracker. +// +// These devices are brought under a common interface +// because there is such a large range of devices out +// there that may support any number of these types of +// controls, we couldn't even begin to cover them with +// type-specific subclasses. +// +// Use the various has_() and get_num_() methods to +// determine information about the device capabilities. +// For instance, has_keyboard() will give an indication +// that you can receive keystroke events from this +// device, and get_num_buttons() will tell you that +// the device may send button events. +// +// There is the DeviceType enumeration, however, which +// will (if known) contain identification about the +// general category of devices this fits in, such as +// keyboard, mouse, gamepad, or flight stick. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE InputDevice : public TypedReferenceCount { +PUBLISHED: + // This enum contains information that can be used to identify the + // type of input device. + enum DeviceClass { + DC_unknown, + + // This means that the device doesn't correspond to a physical + // device, but rather to a dynamic source of input events. + DC_virtual, + + // A physical, alphabetical keyboard. + DC_keyboard, + + DC_mouse, + DC_touch, + + // A gamepad with action buttons, a D-pad, and thumbsticks. + DC_gamepad, + + DC_flight_stick, + DC_steering_wheel, + }; + +protected: + InputDevice(const string &name, DeviceClass dev_class, int flags); + +public: + InputDevice(); + InputDevice(const InputDevice ©); + void operator = (const InputDevice ©); + ~InputDevice(); + +PUBLISHED: + enum ControlAxis { + C_none, + + // Gamepad + C_left_x, + C_left_y, + C_left_trigger, + C_right_x, + C_right_y, + C_right_trigger, + + // Flight stick + C_x, + C_y, + C_trigger, + C_throttle, + }; + + INLINE string get_name() const; + INLINE bool is_connected() const; + INLINE DeviceClass get_device_class() const; + + // The human-readable name of this input device. + MAKE_PROPERTY(name, get_name); + + // This is false if we know that the device is not currently connected. + // May report false positives if we can't know this with certainty. + MAKE_PROPERTY(connected, is_connected); + + // This contains an identification of the general type of device. If + // this could not be determined, it is set to DC_unknown. + MAKE_PROPERTY(device_class, get_device_class); + + INLINE bool has_pointer() const; + INLINE bool has_keyboard() const; + INLINE bool has_tracker() const; + INLINE bool has_battery() const; + + INLINE PointerData get_pointer() const; + INLINE TrackerData get_tracker() const; + + INLINE short get_battery_level() const; + INLINE short get_max_battery_level() const; + + INLINE int get_num_buttons() const; + INLINE void set_button_map(int index, ButtonHandle button); + INLINE ButtonHandle get_button_map(int index) const; + INLINE bool get_button_state(int index) const; + INLINE bool is_button_known(int index) const; + + INLINE int get_num_controls() const; + INLINE void set_control_map(int index, ControlAxis axis); + INLINE ControlAxis get_control_map(int index) const; + INLINE double get_control_state(int index) const; + INLINE bool is_control_known(int index) const; + + INLINE void enable_pointer_events(); + INLINE void disable_pointer_events(); + + void poll(); + + bool has_button_event() const; + PT(ButtonEventList) get_button_events(); + bool has_pointer_event() const; + PT(PointerEventList) get_pointer_events(); + + virtual void output(ostream &out) const; + +protected: + void set_pointer(bool inwin, double x, double y, double time); + void set_pointer_out_of_window(double time); + void set_button_state(int index, bool down); + void set_control_state(int index, double state); + void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); + + virtual void do_poll(); + +public: + INLINE void set_connected(bool connected); + + // We need these methods to make VC++ happy when we try to + // instantiate a pvector. They don't do + // anything useful. + INLINE bool operator == (const InputDevice &other) const; + INLINE bool operator != (const InputDevice &other) const; + INLINE bool operator < (const InputDevice &other) const; + + void output_buttons(ostream &out) const; + void write_buttons(ostream &out, int indent_level) const; + void write_controls(ostream &out, int indent_level) const; + +protected: + enum InputDeviceFlags { + // The device provides absolute screen coordinates. + IDF_has_pointer = 0x01, + + // The device has an interface for providing text input. + IDF_has_keyboard = 0x02, + + // The device has a motion tracker, such as an HMD. + IDF_has_tracker = 0x04, + + // The device can produce force feedback. + IDF_has_vibration = 0x08, + + // The device provides information about battery life. + IDF_has_battery = 0x10, + }; + +protected: + typedef pdeque ButtonEvents; + + LightMutex _lock; + + string _name; + DeviceClass _device_class; + int _flags; + int _event_sequence; + bool _is_connected; + bool _enable_pointer_events; + PointerData _pointer_data; + PT(ButtonEventList) _button_events; + PT(PointerEventList) _pointer_events; + +public: + enum State { + S_unknown, + S_up, + S_down + }; + + class ButtonState { + public: + INLINE ButtonState(); + + ButtonHandle _handle; + State _state; + }; + typedef pvector Buttons; + Buttons _buttons; + + class AnalogState { + public: + INLINE AnalogState(); + + ControlAxis _axis; + double _state; + bool _known; + }; + typedef pvector Controls; + Controls _controls; + + short _battery_level; + short _max_battery_level; + + TrackerData _tracker_data; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedReferenceCount::init_type(); + register_type(_type_handle, "InputDevice", + TypedReferenceCount::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +INLINE ostream &operator << (ostream &out, const InputDevice &device) { + device.output(out); + return out; +} + +ostream &operator << (ostream &out, InputDevice::DeviceClass dc); +ostream &operator << (ostream &out, InputDevice::ControlAxis axis); + +#include "inputDevice.I" + +#endif diff --git a/panda/src/device/inputDeviceManager.I b/panda/src/device/inputDeviceManager.I new file mode 100644 index 0000000000..21f687f621 --- /dev/null +++ b/panda/src/device/inputDeviceManager.I @@ -0,0 +1,27 @@ +// Filename: inputDeviceManager.I +// Created by: rdb (09Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::get_global_ptr +// Access: Published, Static +// Description: Returns the singleton instance. +//////////////////////////////////////////////////////////////////// +INLINE InputDeviceManager *InputDeviceManager:: +get_global_ptr() { + if (_global_ptr == (InputDeviceManager *)NULL) { + _global_ptr = new InputDeviceManager; + } + return _global_ptr; +} diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx new file mode 100644 index 0000000000..d67e21208f --- /dev/null +++ b/panda/src/device/inputDeviceManager.cxx @@ -0,0 +1,240 @@ +// Filename: inputDeviceManager.cxx +// Created by: rdb (09Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "inputDeviceManager.h" +#include "linuxJoystickDevice.h" +#include "throw_event.h" + +#ifdef PHAVE_LINUX_INPUT_H +#include +#include +#include +#include +#endif + +InputDeviceManager *InputDeviceManager::_global_ptr = NULL; + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::Constructor +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +InputDeviceManager:: +InputDeviceManager() : _inotify_fd(-1) { +#ifdef PHAVE_LINUX_INPUT_H + // Scan /dev/input for a list of input devices. + DIR *dir = opendir("/dev/input"); + if (dir) { + dirent *entry = readdir(dir); + while (entry != NULL) { + if (entry->d_type == DT_CHR) { + string name(entry->d_name); + if (!consider_add_linux_device(name)) { + // We can't access it. That's pretty normal for most devices. + if (device_cat.is_debug()) { + device_cat.debug() + << "Ignoring input device /dev/input/" << name << ": " + << strerror(errno) << "\n"; + } + errno = 0; + } + } + entry = readdir(dir); + } + closedir(dir); + } else { + device_cat.error() + << "Error opening directory /dev/input: " << strerror(errno) << "\n"; + return; + } + + // Use inotify to watch /dev/input for hotplugging of devices. + _inotify_fd = inotify_init1(O_NONBLOCK); + + if (_inotify_fd < 0) { + device_cat.error() + << "Error initializing inotify: " << strerror(errno) << "\n"; + + } else if (inotify_add_watch(_inotify_fd, "/dev/input", IN_CREATE | IN_ATTRIB | IN_DELETE) < 0) { + device_cat.error() + << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; + } +#endif +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::Destructor +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +InputDeviceManager:: +~InputDeviceManager() { +#ifdef PHAVE_LINUX_INPUT_H + if (_inotify_fd >= 0) { + close(_inotify_fd); + _inotify_fd = -1; + } +#endif +} + +#ifdef PHAVE_LINUX_INPUT_H +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::consider_add_linux_device +// Access: Private +// Description: Checks whether the given device is accessible, and +// if so, adds it. Returns false on error. +//////////////////////////////////////////////////////////////////// +bool InputDeviceManager:: +consider_add_linux_device(const string &name) { + // Get the full path name first. + string path = "/dev/input/"; + path += name; + + if (access(path.c_str(), R_OK) < 0) { + return false; + } + + if (_devices_by_path.count(name)) { + // We already have this device. + return true; + } + + // Check if it's a joystick or game controller device. + if (name.size() > 2 && name[0] == 'j' && name[1] == 's' && isdigit(name[2])) { + PT(InputDevice) device = new LinuxJoystickDevice(path); + if (device_cat.is_info()) { + device_cat.info() + << "Discovered input device " << *device << "\n"; + } + + _devices_by_path[name] = device; + _all_devices.push_back(MOVE(device)); + return true; + } + + return true; +} +#endif + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::add_device +// Access: Public +// Description: Called when a new device has been discovered. +//////////////////////////////////////////////////////////////////// +void InputDeviceManager:: +add_device(InputDevice *device) { + { + LightMutexHolder holder(_lock); + _all_devices.push_back(device); + } + throw_event("device-added", device); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::remove_device +// Access: Public +// Description: Called when a device has been removed. +//////////////////////////////////////////////////////////////////// +void InputDeviceManager:: +remove_device(InputDevice *device) { + { + LightMutexHolder holder(_lock); + + InputDevices::iterator it; + it = std::find(_all_devices.begin(), _all_devices.end(), device); + nassertv_always(it != _all_devices.end()); + + _all_devices.erase(it); + } + + throw_event("device-removed", device); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::poll +// Access: Public +// Description: Polls the system to see if there are any new devices. +//////////////////////////////////////////////////////////////////// +void InputDeviceManager:: +poll() { +#ifdef PHAVE_LINUX_INPUT_H + // We use inotify to tell us whether a device was added, removed, + // or has changed permissions to allow us to access it. + unsigned int avail; + ioctl(_inotify_fd, FIONREAD, &avail); + if (avail == 0) { + return; + } + + char buffer[avail]; + int n_read = read(_inotify_fd, buffer, avail); + if (n_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available for now. + + } else { + device_cat.error() << "read: " << strerror(errno) << "\n"; + } + return; + } + + LightMutexHolder holder(_lock); + + // Iterate over the events in the buffer. + char *ptr = buffer; + char *end = buffer + avail; + while (ptr < end) { + inotify_event *event = (inotify_event *)ptr; + + string name(event->name); + + if (event->mask & IN_DELETE) { + // The device was deleted. If we have it, remove it. + DevicesByPath::iterator it = _devices_by_path.find(name); + if (it != _devices_by_path.end()) { + PT(InputDevice) device = it->second; + device->set_connected(false); + + InputDevices::iterator it2; + _devices_by_path.erase(it); + it2 = std::find(_all_devices.begin(), _all_devices.end(), device); + _all_devices.erase(it2); + + if (device_cat.is_info()) { + device_cat.info() + << "Removed input device " << *device << "\n"; + } + throw_event("device-removed", device.p()); + } + + } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { + // The device was created, or it was chmodded to be accessible. + DevicesByPath::iterator it = _devices_by_path.find(name); + if (it == _devices_by_path.end()) { + // We don't know about this device yet. + if (!consider_add_linux_device(name) && (event->mask & IN_CREATE) != 0) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Ignoring input device /dev/input/" << name << ": " + << strerror(errno) << "\n"; + } + } + errno = 0; + } + } + + ptr += sizeof(inotify_event) + event->len; + } +#endif +} diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h new file mode 100644 index 0000000000..3c05cf96fd --- /dev/null +++ b/panda/src/device/inputDeviceManager.h @@ -0,0 +1,64 @@ +// Filename: inputDeviceManager.h +// Created by: rdb (09Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef INPUTDEVICEMANAGER_H +#define INPUTDEVICEMANAGER_H + +#include "pandabase.h" +#include "lightMutex.h" +#include "inputDevice.h" + +//////////////////////////////////////////////////////////////////// +// Class : InputDeviceManager +// Description : This class keeps track of all the devices on a +// system, and sends out events when a device has been +// hot-plugged. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE InputDeviceManager { +private: + InputDeviceManager(); + ~InputDeviceManager(); + +#ifdef PHAVE_LINUX_INPUT_H + bool consider_add_linux_device(const string &name); +#endif + +public: + void add_device(InputDevice *device); + void remove_device(InputDevice *device); + +PUBLISHED: + void poll(); + + INLINE static InputDeviceManager *get_global_ptr(); + +private: + LightMutex _lock; + +#ifdef PHAVE_LINUX_INPUT_H + int _inotify_fd; + + typedef pmap DevicesByPath; + DevicesByPath _devices_by_path; +#endif + + typedef pvector InputDevices; + InputDevices _all_devices; + + static InputDeviceManager *_global_ptr; +}; + +#include "inputDeviceManager.I" + +#endif diff --git a/panda/src/device/linuxJoystickDevice.I b/panda/src/device/linuxJoystickDevice.I new file mode 100644 index 0000000000..b1150e12a5 --- /dev/null +++ b/panda/src/device/linuxJoystickDevice.I @@ -0,0 +1,14 @@ +// Filename: linuxJoystickDevice.I +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx new file mode 100644 index 0000000000..b3566554e6 --- /dev/null +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -0,0 +1,313 @@ +// Filename: linuxJoystickDevice.cxx +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "linuxJoystickDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +#include "gamepadButton.h" + +#include +#include + +TypeHandle LinuxJoystickDevice::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::Constructor +// Access: Private +// Description: Creates a new device using the Linux joystick +// device using the given device filename. +//////////////////////////////////////////////////////////////////// +LinuxJoystickDevice:: +LinuxJoystickDevice(const string &device) : + _fd(-1), + _device(device) +{ + LightMutexHolder holder(_lock); + if (!open_device()) { + device_cat.error() + << "Could not open joystick device " << _device + << ": " << strerror(errno) << "\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +LinuxJoystickDevice:: +~LinuxJoystickDevice() { + if (_fd != -1) { + close(_fd); + _fd = -1; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::do_poll +// Access: Private, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void LinuxJoystickDevice:: +do_poll() { + if (_fd != -1) { + while (process_events()) {} + } else { + open_device(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::open_device +// Access: Private +// Description: Opens or reopens the joystick device, and reads out +// the button and axis mappings. +// Assumes the lock has been held. +//////////////////////////////////////////////////////////////////// +bool LinuxJoystickDevice:: +open_device() { + nassertr(_lock.debug_is_locked(), false); + + _fd = open(_device.c_str(), O_RDONLY | O_NONBLOCK); + + if (_fd == -1) { + _is_connected = false; + return false; + } + + // Read the name from the device. + char name[128]; + name[0] = 0; + ioctl(_fd, JSIOCGNAME(sizeof(name)), name); + _name = name; + + // Get the number of axes. + PN_uint8 num_axes = 0, num_buttons = 0; + ioctl(_fd, JSIOCGAXES, &num_axes); + ioctl(_fd, JSIOCGBUTTONS, &num_buttons); + + _buttons.resize(num_buttons); + _controls.resize(num_axes); + + if (num_buttons > 0) { + PN_uint16 btnmap[512]; + ioctl(_fd, JSIOCGBTNMAP, btnmap); + + for (char i = 0; i < num_buttons; ++i) { + ButtonHandle handle = ButtonHandle::none(); + switch (btnmap[i]) { + case BTN_A: + handle = GamepadButton::action_a(); + _device_class = DC_gamepad; + break; + + case BTN_B: + handle = GamepadButton::action_b(); + break; + + case BTN_C: + handle = GamepadButton::action_c(); + break; + + case BTN_X: + handle = GamepadButton::action_x(); + break; + + case BTN_Y: + handle = GamepadButton::action_y(); + break; + + case BTN_Z: + handle = GamepadButton::action_z(); + break; + + case BTN_TL: + handle = GamepadButton::lshoulder(); + break; + + case BTN_TR: + handle = GamepadButton::rshoulder(); + break; + + case BTN_TL2: + handle = GamepadButton::ltrigger(); + break; + + case BTN_TR2: + handle = GamepadButton::rtrigger(); + break; + + case BTN_SELECT: + handle = GamepadButton::back(); + break; + + case BTN_START: + handle = GamepadButton::start(); + break; + + case BTN_MODE: + handle = GamepadButton::guide(); + break; + + case BTN_THUMBL: + handle = GamepadButton::lstick(); + break; + + case BTN_THUMBR: + handle = GamepadButton::rstick(); + break; + + case BTN_TRIGGER_HAPPY1: + handle = GamepadButton::dpad_left(); + break; + + case BTN_TRIGGER_HAPPY2: + handle = GamepadButton::dpad_right(); + break; + + case BTN_TRIGGER_HAPPY3: + handle = GamepadButton::dpad_up(); + break; + + case BTN_TRIGGER_HAPPY4: + handle = GamepadButton::dpad_down(); + break; + + default: + handle = ButtonHandle::none(); + break; + } + _buttons[i]._handle = handle; + } + } + + if (num_axes > 0) { + PN_uint8 axmap[512]; + ioctl(_fd, JSIOCGAXMAP, axmap); + + for (char i = 0; i < num_axes; ++i) { + ControlAxis axis = C_none; + + switch (axmap[i]) { + case ABS_X: + if (_device_class == DC_gamepad) { + axis = C_left_x; + } else { + axis = C_x; + } + break; + + case ABS_Y: + if (_device_class == DC_gamepad) { + axis = C_left_y; + } else { + axis = C_y; + } + break; + + case ABS_Z: + if (_device_class == DC_gamepad) { + axis = C_left_trigger; + } else { + axis = C_trigger; + } + break; + + case ABS_RX: + axis = C_right_x; + break; + + case ABS_RY: + axis = C_right_y; + break; + + case ABS_RZ: + axis = C_right_trigger; + break; + + default: + axis = C_none; + break; + } + _controls[i]._axis = axis; + } + } + + // Read the init events. + _is_connected = true; + while (process_events()) {}; + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::process_events +// Access: Private +// Description: Reads a number of events from the joystick. +//////////////////////////////////////////////////////////////////// +bool LinuxJoystickDevice:: +process_events() { + // Read 8 events at a time. + struct js_event events[8]; + + int n_read = read(_fd, events, sizeof(events)); + if (n_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available for now. + + } else if (errno == ENODEV) { + // The device ceased to exist, so we better close it. + close(_fd); + _fd = -1; + _is_connected = false; + errno = 0; + + } else { + device_cat.error() << "read: " << strerror(errno) << "\n"; + } + return false; + } + + if (n_read == 0) { + return false; + } + + n_read /= sizeof(struct js_event); + + for (int i = 0; i < n_read; ++i) { + int index = events[i].number; + + if (events[i].type & JS_EVENT_BUTTON) { + set_button_state(index, (events[i].value != 0)); + + } else if (events[i].type & JS_EVENT_AXIS) { + ControlAxis axis = _controls[index]._axis; + + if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { + // We'd like to use 0.0 to indicate the resting position. + set_control_state(index, (events[i].value + 32767) / 65534.0); + } else { + set_control_state(index, events[i].value / 32767.0); + } + } + } + + return true; +} + +#endif diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h new file mode 100644 index 0000000000..d71ad45c17 --- /dev/null +++ b/panda/src/device/linuxJoystickDevice.h @@ -0,0 +1,60 @@ +// Filename: linuxJoystickDevice.h +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef LINUXJOYSTICKDEVICE_H +#define LINUXJOYSTICKDEVICE_H + +#include "inputDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +//////////////////////////////////////////////////////////////////// +// Class : LinuxJoystickDevice +// Description : This is a type of device that uses the Linux +// /dev/input/js# API to read data from a game controller. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice { +PUBLISHED: + LinuxJoystickDevice(const string &device); + virtual ~LinuxJoystickDevice(); + +private: + virtual void do_poll(); + + bool open_device(); + bool process_events(); + +private: + int _fd; + string _device; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + InputDevice::init_type(); + register_type(_type_handle, "LinuxJoystickDevice", + InputDevice::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "linuxJoystickDevice.I" + +#endif // PHAVE_LINUX_INPUT_H + +#endif // LINUXJOYSTICKDEVICE_H diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index b48fae48ec..80fa832775 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -5,4 +5,7 @@ #include "clientDevice.cxx" #include "clientDialDevice.cxx" #include "clientTrackerDevice.cxx" - +#include "evdevInputDevice.cxx" +#include "inputDevice.cxx" +#include "inputDeviceManager.cxx" +#include "linuxJoystickDevice.cxx" diff --git a/panda/src/device/p3device_composite2.cxx b/panda/src/device/p3device_composite2.cxx index 9457fca14c..294e5d69ae 100644 --- a/panda/src/device/p3device_composite2.cxx +++ b/panda/src/device/p3device_composite2.cxx @@ -3,7 +3,6 @@ #include "analogNode.cxx" #include "buttonNode.cxx" #include "dialNode.cxx" -#include "mouseAndKeyboard.cxx" #include "trackerData.cxx" #include "trackerNode.cxx" #include "virtualMouse.cxx" diff --git a/panda/src/device/trackerNode.I b/panda/src/device/trackerNode.I index 5f872c7ea3..5aa067e730 100644 --- a/panda/src/device/trackerNode.I +++ b/panda/src/device/trackerNode.I @@ -20,10 +20,9 @@ //////////////////////////////////////////////////////////////////// INLINE bool TrackerNode:: is_valid() const { - return (_tracker != (ClientTrackerDevice *)NULL) && _tracker->is_connected(); + return (!_tracker.is_null()/* && _tracker->is_connected()*/); } - //////////////////////////////////////////////////////////////////// // Function: TrackerNode::get_pos // Access: Public diff --git a/panda/src/device/trackerNode.cxx b/panda/src/device/trackerNode.cxx index edc20ca07d..095fbfc141 100644 --- a/panda/src/device/trackerNode.cxx +++ b/panda/src/device/trackerNode.cxx @@ -51,7 +51,7 @@ TrackerNode(ClientBase *client, const string &device_name) : return; } - _tracker = DCAST(ClientTrackerDevice, device); + _tracker = device; } //////////////////////////////////////////////////////////////////// @@ -60,18 +60,19 @@ TrackerNode(ClientBase *client, const string &device_name) : // Description: //////////////////////////////////////////////////////////////////// TrackerNode:: -TrackerNode(ClientTrackerDevice *device) : - DataNode(device->get_device_name()), +TrackerNode(InputDevice *device) : + DataNode(device->get_name()), _tracker(device) { _transform_output = define_output("transform", TransformState::get_class_type()); _transform = TransformState::make_identity(); - nassertv(device != (ClientTrackerDevice *)NULL); - ClientBase *client = device->get_client(); - nassertv(client != (ClientBase *)NULL); - set_tracker_coordinate_system(client->get_coordinate_system()); + nassertv(device != (InputDevice *)NULL); + nassertv(device->has_tracker()); + + //TODO: get coordinate system from tracker object? + set_tracker_coordinate_system(CS_default); set_graph_coordinate_system(CS_default); } @@ -105,9 +106,7 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { if (is_valid()) { _tracker->poll(); - _tracker->acquire(); - _data = _tracker->get_data(); - _tracker->unlock(); + _data = _tracker->get_tracker(); _data.get_orient().extract_to_matrix(_mat); if (_tracker_cs != _graph_cs) { diff --git a/panda/src/device/trackerNode.h b/panda/src/device/trackerNode.h index 8010c9da7b..9cef1c2d7f 100644 --- a/panda/src/device/trackerNode.h +++ b/panda/src/device/trackerNode.h @@ -19,7 +19,7 @@ #include "clientBase.h" #include "trackerData.h" -#include "clientTrackerDevice.h" +#include "inputDevice.h" #include "dataNode.h" #include "luse.h" #include "linmath_events.h" @@ -27,16 +27,16 @@ //////////////////////////////////////////////////////////////////// // Class : TrackerNode -// Description : This is the primary interface to a Tracker object -// associated with a ClientBase. It reads the position -// and orientation information from the tracker and -// makes it available as a transformation on the data -// graph. +// Description : This class reads the position and orientation +// information from a tracker device and makes it +// available as a transformation on the data graph. +// It is also the primary interface to a Tracker object +// associated with a ClientBase. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_DEVICE TrackerNode : public DataNode { PUBLISHED: TrackerNode(ClientBase *client, const string &device_name); - TrackerNode(ClientTrackerDevice *device); + TrackerNode(InputDevice *device); virtual ~TrackerNode(); INLINE bool is_valid() const; @@ -66,7 +66,7 @@ private: CPT(TransformState) _transform; private: - PT(ClientTrackerDevice) _tracker; + PT(InputDevice) _tracker; TrackerData _data; LMatrix4 _mat; CoordinateSystem _tracker_cs, _graph_cs; diff --git a/panda/src/display/callbackGraphicsWindow.cxx b/panda/src/display/callbackGraphicsWindow.cxx index 6de33cc9d6..af1d4169e7 100644 --- a/panda/src/display/callbackGraphicsWindow.cxx +++ b/panda/src/display/callbackGraphicsWindow.cxx @@ -54,22 +54,6 @@ CallbackGraphicsWindow:: ~CallbackGraphicsWindow() { } - -//////////////////////////////////////////////////////////////////// -// Function: CallbackGraphicsWindow::get_input_device -// Access: Published -// Description: Returns a writable reference to the nth input device -// (mouse). This is intended to be used for the window -// implementation to record mouse and keyboard input -// information for the Panda system. -//////////////////////////////////////////////////////////////////// -GraphicsWindowInputDevice &CallbackGraphicsWindow:: -get_input_device(int device) { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), _input_devices[0]); - return _input_devices[device]; -} - //////////////////////////////////////////////////////////////////// // Function: CallbackGraphicsWindow::create_input_device // Access: Published @@ -79,9 +63,7 @@ get_input_device(int device) { //////////////////////////////////////////////////////////////////// int CallbackGraphicsWindow:: create_input_device(const string &name) { - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard(this, name); - return add_input_device(device); + return add_input_device(GraphicsWindowInputDevice::pointer_and_keyboard(this, name)); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/display/callbackGraphicsWindow.h b/panda/src/display/callbackGraphicsWindow.h index c34d324126..e1633959db 100644 --- a/panda/src/display/callbackGraphicsWindow.h +++ b/panda/src/display/callbackGraphicsWindow.h @@ -182,7 +182,6 @@ PUBLISHED: INLINE void clear_render_callback(); INLINE CallbackObject *get_render_callback() const; - GraphicsWindowInputDevice &get_input_device(int device); int create_input_device(const string &name); public: diff --git a/panda/src/display/config_display.cxx b/panda/src/display/config_display.cxx index a62e6a4fb0..f98508894b 100644 --- a/panda/src/display/config_display.cxx +++ b/panda/src/display/config_display.cxx @@ -22,9 +22,11 @@ #include "graphicsPipe.h" #include "graphicsOutput.h" #include "graphicsBuffer.h" -#include "graphicsWindow.h" #include "graphicsDevice.h" +#include "graphicsWindow.h" +#include "graphicsWindowInputDevice.h" #include "graphicsWindowProcCallbackData.h" +#include "mouseAndKeyboard.h" #include "nativeWindowHandle.h" #include "parasiteBuffer.h" #include "pandaSystem.h" @@ -501,7 +503,9 @@ init_libdisplay() { GraphicsPipe::init_type(); GraphicsStateGuardian::init_type(); GraphicsWindow::init_type(); + GraphicsWindowInputDevice::init_type(); GraphicsWindowProcCallbackData::init_type(); + MouseAndKeyboard::init_type(); NativeWindowHandle::init_type(); ParasiteBuffer::init_type(); StandardMunger::init_type(); diff --git a/panda/src/display/graphicsWindow.cxx b/panda/src/display/graphicsWindow.cxx index 6166f9cbac..b6d4976181 100644 --- a/panda/src/display/graphicsWindow.cxx +++ b/panda/src/display/graphicsWindow.cxx @@ -288,6 +288,20 @@ get_num_input_devices() const { return result; } +//////////////////////////////////////////////////////////////////// +// Function: GraphicsWindow::get_input_device +// Access: Published +// Description: Returns the nth input device associated with the +// window. Typically, a window will have exactly one +// input device: the keyboard/mouse pair. +//////////////////////////////////////////////////////////////////// +InputDevice *GraphicsWindow:: +get_input_device(int device) const { + LightMutexHolder holder(_input_lock); + nassertr(device >= 0 && device < (int)_input_devices.size(), NULL); + return _input_devices[device]; +} + //////////////////////////////////////////////////////////////////// // Function: GraphicsWindow::get_input_device_name // Access: Published @@ -299,7 +313,7 @@ get_input_device_name(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), ""); - result = _input_devices[device].get_name(); + result = _input_devices[device]->get_name(); } return result; } @@ -317,7 +331,7 @@ has_pointer(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), false); - result = _input_devices[device].has_pointer(); + result = _input_devices[device]->has_pointer(); } return result; } @@ -334,7 +348,7 @@ has_keyboard(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), false); - result = _input_devices[device].has_keyboard(); + result = _input_devices[device]->has_keyboard(); } return result; } @@ -359,7 +373,7 @@ void GraphicsWindow:: enable_pointer_events(int device) { LightMutexHolder holder(_input_lock); nassertv(device >= 0 && device < (int)_input_devices.size()); - _input_devices[device].enable_pointer_events(); + _input_devices[device]->enable_pointer_events(); } //////////////////////////////////////////////////////////////////// @@ -371,7 +385,7 @@ void GraphicsWindow:: disable_pointer_events(int device) { LightMutexHolder holder(_input_lock); nassertv(device >= 0 && device < (int)_input_devices.size()); - _input_devices[device].disable_pointer_events(); + _input_devices[device]->disable_pointer_events(); } //////////////////////////////////////////////////////////////////// @@ -379,11 +393,11 @@ disable_pointer_events(int device) { // Access: Published // Description: See GraphicsWindowInputDevice::enable_pointer_mode //////////////////////////////////////////////////////////////////// -void GraphicsWindow:: +/*void GraphicsWindow:: enable_pointer_mode(int device, double speed) { LightMutexHolder holder(_input_lock); nassertv(device >= 0 && device < (int)_input_devices.size()); - _input_devices[device].enable_pointer_mode(speed); + _input_devices[device]->enable_pointer_mode(speed); } //////////////////////////////////////////////////////////////////// @@ -395,8 +409,8 @@ void GraphicsWindow:: disable_pointer_mode(int device) { LightMutexHolder holder(_input_lock); nassertv(device >= 0 && device < (int)_input_devices.size()); - _input_devices[device].disable_pointer_mode(); -} + _input_devices[device]->disable_pointer_mode(); +}*/ //////////////////////////////////////////////////////////////////// // Function: GraphicsWindow::get_pointer @@ -412,7 +426,7 @@ get_pointer(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData()); - result = _input_devices[device].get_pointer(); + result = _input_devices[device]->get_pointer(); } return result; } @@ -443,81 +457,6 @@ close_ime() { return; } -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindow::has_button_event -// Access: Public -// Description: Returns true if the indicated device has a pending -// button event (a mouse button or keyboard button -// down/up), false otherwise. If this returns true, the -// particular event may be extracted via -// get_button_event(). -//////////////////////////////////////////////////////////////////// -bool GraphicsWindow:: -has_button_event(int device) const { - bool result; - { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), false); - result = _input_devices[device].has_button_event(); - } - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindow::get_button_event -// Access: Public -// Description: Assuming a previous call to has_button_event() -// returned true, this returns the pending button event. -//////////////////////////////////////////////////////////////////// -ButtonEvent GraphicsWindow:: -get_button_event(int device) { - ButtonEvent result; - { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), ButtonEvent()); - nassertr(_input_devices[device].has_button_event(), ButtonEvent()); - result = _input_devices[device].get_button_event(); - } - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindow::has_pointer_event -// Access: Public -// Description: Returns true if the indicated device has a pending -// pointer event (a mouse movement). If this returns -// true, the particular event may be extracted via -// get_pointer_events(). -//////////////////////////////////////////////////////////////////// -bool GraphicsWindow:: -has_pointer_event(int device) const { - bool result; - { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), false); - result = _input_devices[device].has_pointer_event(); - } - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindow::get_pointer_events -// Access: Public -// Description: Assuming a previous call to has_pointer_event() -// returned true, this returns the pending pointer event list. -//////////////////////////////////////////////////////////////////// -PT(PointerEventList) GraphicsWindow:: -get_pointer_events(int device) { - PT(PointerEventList) result; - { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), NULL); - nassertr(_input_devices[device].has_pointer_event(), NULL); - result = _input_devices[device].get_pointer_events(); - } - return result; -} - //////////////////////////////////////////////////////////////////// // Function: GraphicsWindow::verify_window_sizes // Access: Public, Virtual @@ -860,11 +799,15 @@ system_changed_size(int x_size, int y_size) { // Returns the index of the new device. //////////////////////////////////////////////////////////////////// int GraphicsWindow:: -add_input_device(const GraphicsWindowInputDevice &device) { +add_input_device(InputDevice *device) { LightMutexHolder holder(_input_lock); int index = (int)_input_devices.size(); _input_devices.push_back(device); - _input_devices.back().set_device_index(index); + + if (device->is_of_type(GraphicsWindowInputDevice::get_class_type())) { + ((GraphicsWindowInputDevice *)device)->set_device_index(index); + } + return index; } diff --git a/panda/src/display/graphicsWindow.h b/panda/src/display/graphicsWindow.h index e7ebab8c80..dbeae1e1da 100644 --- a/panda/src/display/graphicsWindow.h +++ b/panda/src/display/graphicsWindow.h @@ -82,10 +82,12 @@ PUBLISHED: INLINE WindowHandle *get_window_handle() const; MAKE_PROPERTY(window_handle, get_window_handle); - + // Mouse and keyboard routines int get_num_input_devices() const; + InputDevice *get_input_device(int i) const; string get_input_device_name(int device) const; + MAKE_SEQ(get_input_devices, get_num_input_devices, get_input_device); MAKE_SEQ(get_input_device_names, get_num_input_devices, get_input_device_name); bool has_pointer(int device) const; bool has_keyboard(int device) const; @@ -93,20 +95,14 @@ PUBLISHED: void enable_pointer_events(int device); void disable_pointer_events(int device); - void enable_pointer_mode(int device, double speed); - void disable_pointer_mode(int device); + /*void enable_pointer_mode(int device, double speed); + void disable_pointer_mode(int device);*/ MouseData get_pointer(int device) const; virtual bool move_pointer(int device, int x, int y); virtual void close_ime(); public: - // No need to publish these. - bool has_button_event(int device) const; - ButtonEvent get_button_event(int device); - bool has_pointer_event(int device) const; - PT(PointerEventList) get_pointer_events(int device); - virtual void add_window_proc( const GraphicsWindowProc* wnd_proc_object ){}; virtual void remove_window_proc( const GraphicsWindowProc* wnd_proc_object ){}; virtual void clear_window_procs(){}; @@ -146,8 +142,8 @@ protected: void system_changed_size(int x_size, int y_size); protected: - int add_input_device(const GraphicsWindowInputDevice &device); - typedef vector_GraphicsWindowInputDevice InputDevices; + int add_input_device(InputDevice *device); + typedef pvector InputDevices; InputDevices _input_devices; LightMutex _input_lock; diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index 3763d7baa4..ea26364be7 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -12,73 +12,14 @@ // //////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////// // Function: GraphicsWindowInputDevice::Default Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// INLINE GraphicsWindowInputDevice:: -GraphicsWindowInputDevice() { - LightMutexHolder holder(_lock); - _flags = 0; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_name -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE string GraphicsWindowInputDevice:: -get_name() const { - LightMutexHolder holder(_lock); - return _name; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::has_pointer -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -has_pointer() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_pointer) != 0); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::has_keyboard -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -has_keyboard() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_keyboard) != 0); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_pointer -// Access: Public -// Description: Returns the MouseData associated with the input -// device's pointer. -//////////////////////////////////////////////////////////////////// -INLINE MouseData GraphicsWindowInputDevice:: -get_pointer() const { - LightMutexHolder holder(_lock); - return _mouse_data; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_raw_pointer -// Access: Public -// Description: Returns the MouseData associated with the input -// device's pointer, in raw form (ie, prior to any -// pointer_mode interpretation). -//////////////////////////////////////////////////////////////////// -INLINE MouseData GraphicsWindowInputDevice:: -get_raw_pointer() const { - LightMutexHolder holder(_lock); - return _true_mouse_data; +GraphicsWindowInputDevice() : _host(NULL) { } //////////////////////////////////////////////////////////////////// @@ -90,140 +31,9 @@ get_raw_pointer() const { //////////////////////////////////////////////////////////////////// INLINE void GraphicsWindowInputDevice:: set_device_index(int index) { - LightMutexHolder holder(_lock); _device_index = index; } -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::enable_pointer_events -// Access: Public -// Description: Enables the generation of mouse-movement events. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -enable_pointer_events() { - LightMutexHolder holder(_lock); - _enable_pointer_events = true; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::disable_pointer_events -// Access: Public -// Description: Disables the generation of mouse-movement events. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -disable_pointer_events() { - LightMutexHolder holder(_lock); - _enable_pointer_events = false; - _pointer_events.clear(); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::button_down -// Access: Published -// Description: Records that the indicated button has been depressed. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -button_down(ButtonHandle button) { - button_down(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::button_resume_down -// Access: Published -// Description: Records that the indicated button was depressed -// earlier, and we only just detected the event after -// the fact. This is mainly useful for tracking the -// state of modifier keys. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -button_resume_down(ButtonHandle button) { - button_resume_down(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::button_up -// Access: Published -// Description: Records that the indicated button has been released. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -button_up(ButtonHandle button) { - button_up(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::keystroke -// Access: Published -// Description: Records that the indicated keystroke has been -// generated. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -keystroke(int keycode) { - keystroke(keycode, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::focus_lost -// Access: Published -// Description: This should be called when the window focus is lost, -// so that we may miss upcoming button events -// (especially "up" events) for the next period of time. -// It generates keyboard and mouse "up" events for those -// buttons that we previously sent unpaired "down" -// events, so that the Panda application will believe -// all buttons are now released. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -focus_lost() { - focus_lost(ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::raw_button_down -// Access: Published -// Description: Records that the indicated button has been depressed. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -raw_button_down(ButtonHandle button) { - raw_button_down(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::raw_button_up -// Access: Published -// Description: Records that the indicated button has been released. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -raw_button_up(ButtonHandle button) { - raw_button_up(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::set_pointer_in_window -// Access: Published -// Description: To be called by a particular kind of GraphicsWindow -// to indicate that the pointer is within the window, at -// the given pixel coordinates. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -set_pointer_in_window(double x, double y) { - // mutex is handled in set pointer .. convience function - set_pointer(true, x, y, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::set_pointer_out_of_window -// Access: Published -// Description: To be called by a particular kind of GraphicsWindow -// to indicate that the pointer is no longer within the -// window. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -set_pointer_out_of_window() { - // mutex is handled in set pointer .. convience function - set_pointer(false, _mouse_data._xpos, _mouse_data._ypos, - ClockObject::get_global_clock()->get_frame_time()); -} - //////////////////////////////////////////////////////////////////// // Function: GraphicsWindowInputDevice::set_pointer_in_window // Access: Published @@ -233,8 +43,8 @@ set_pointer_out_of_window() { //////////////////////////////////////////////////////////////////// INLINE void GraphicsWindowInputDevice:: set_pointer_in_window(double x, double y, double time) { - // mutex is handled in set pointer .. convience function - set_pointer(true, x, y, time); + LightMutexHolder holder(_lock); + InputDevice::set_pointer(true, x, y, time); } //////////////////////////////////////////////////////////////////// @@ -246,36 +56,6 @@ set_pointer_in_window(double x, double y, double time) { //////////////////////////////////////////////////////////////////// INLINE void GraphicsWindowInputDevice:: set_pointer_out_of_window(double time) { - // mutex is handled in set pointer .. convience function - set_pointer(false, _mouse_data._xpos, _mouse_data._ypos, time); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::operator == -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -operator == (const GraphicsWindowInputDevice &) const { - return true; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::operator != -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -operator != (const GraphicsWindowInputDevice &) const { - return false; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::operator < -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -operator < (const GraphicsWindowInputDevice &) const { - return false; + LightMutexHolder holder(_lock); + InputDevice::set_pointer_out_of_window(time); } diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index a1d4950205..e7a3c360b4 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -18,17 +18,7 @@ #include "mouseButton.h" #include "keyboardButton.h" -#define EXPCL EXPCL_PANDA_DISPLAY -#define EXPTP EXPTP_PANDA_DISPLAY -#define TYPE GraphicsWindowInputDevice -#define NAME vector_GraphicsWindowInputDevice - -#include "vector_src.cxx" - -// Tell GCC that we'll take care of the instantiation explicitly here. -#ifdef __GNUC__ -#pragma implementation -#endif +TypeHandle GraphicsWindowInputDevice::_type_handle; //////////////////////////////////////////////////////////////////// // Function: GraphicsWindowInputDevice::Constructor @@ -43,14 +33,8 @@ //////////////////////////////////////////////////////////////////// GraphicsWindowInputDevice:: GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : - _host(host), - _name(name), - _flags(flags), - _device_index(0), - _event_sequence(0), - _pointer_mode_enable(false), - _pointer_speed(1.0), - _enable_pointer_events(false) + InputDevice(name, DC_virtual, flags), + _host(host) { } @@ -60,9 +44,9 @@ GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : // Description: This named constructor returns an input device that // only has a pointing device, no keyboard. //////////////////////////////////////////////////////////////////// -GraphicsWindowInputDevice GraphicsWindowInputDevice:: +PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: pointer_only(GraphicsWindow *host, const string &name) { - return GraphicsWindowInputDevice(host, name, IDF_has_pointer); + return new GraphicsWindowInputDevice(host, name, IDF_has_pointer); } //////////////////////////////////////////////////////////////////// @@ -71,9 +55,9 @@ pointer_only(GraphicsWindow *host, const string &name) { // Description: This named constructor returns an input device that // only has a keyboard, no pointing device. //////////////////////////////////////////////////////////////////// -GraphicsWindowInputDevice GraphicsWindowInputDevice:: +PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: keyboard_only(GraphicsWindow *host, const string &name) { - return GraphicsWindowInputDevice(host, name, IDF_has_keyboard); + return new GraphicsWindowInputDevice(host, name, IDF_has_keyboard); } //////////////////////////////////////////////////////////////////// @@ -82,9 +66,9 @@ keyboard_only(GraphicsWindow *host, const string &name) { // Description: This named constructor returns an input device that // has both a keyboard and pointer. //////////////////////////////////////////////////////////////////// -GraphicsWindowInputDevice GraphicsWindowInputDevice:: +PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: pointer_and_keyboard(GraphicsWindow *host, const string &name) { - return + return new GraphicsWindowInputDevice(host, name, IDF_has_pointer | IDF_has_keyboard); } @@ -94,9 +78,8 @@ pointer_and_keyboard(GraphicsWindow *host, const string &name) { // Description: //////////////////////////////////////////////////////////////////// GraphicsWindowInputDevice:: -GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) -{ - *this = copy; +GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) { + *this = copy; } //////////////////////////////////////////////////////////////////// @@ -105,22 +88,9 @@ GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) // Description: //////////////////////////////////////////////////////////////////// void GraphicsWindowInputDevice:: -operator = (const GraphicsWindowInputDevice ©) -{ - LightMutexHolder holder(_lock); - LightMutexHolder holder1(copy._lock); +operator = (const GraphicsWindowInputDevice ©) { _host = copy._host; - _name = copy._name; - _flags = copy._flags; - _device_index = copy._device_index; - _event_sequence = copy._event_sequence; - _pointer_mode_enable = copy._pointer_mode_enable; - _pointer_speed = copy._pointer_speed; - _enable_pointer_events = copy._enable_pointer_events; - _mouse_data = copy._mouse_data; - _true_mouse_data = copy._true_mouse_data; - _button_events = copy._button_events; - _pointer_events = copy._pointer_events; + InputDevice::operator = (copy); } //////////////////////////////////////////////////////////////////// @@ -132,151 +102,6 @@ GraphicsWindowInputDevice:: ~GraphicsWindowInputDevice() { } -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::has_button_event -// Access: Public -// Description: Returns true if this device has a pending button -// event (a mouse button or keyboard button down/up), -// false otherwise. If this returns true, the -// particular event may be extracted via -// get_button_event(). -//////////////////////////////////////////////////////////////////// -bool GraphicsWindowInputDevice:: -has_button_event() const { - LightMutexHolder holder(_lock); - return !_button_events.empty(); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_button_event -// Access: Public -// Description: Assuming a previous call to has_button_event() -// returned true, this returns the pending button event. -//////////////////////////////////////////////////////////////////// -ButtonEvent GraphicsWindowInputDevice:: -get_button_event() { - LightMutexHolder holder(_lock); - ButtonEvent be = _button_events.front(); - _button_events.pop_front(); - return be; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::has_pointer_event -// Access: Public -// Description: Returns true if this device has a pending pointer -// event (a mouse movement), or false otherwise. If -// this returns true, the particular event may be -// extracted via get_pointer_event(). -//////////////////////////////////////////////////////////////////// -bool GraphicsWindowInputDevice:: -has_pointer_event() const { - LightMutexHolder holder(_lock); - return (_pointer_events != 0); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_pointer_events -// Access: Public -// Description: Returns a PointerEventList containing all the recent -// pointer events. -//////////////////////////////////////////////////////////////////// -PT(PointerEventList) GraphicsWindowInputDevice:: -get_pointer_events() { - LightMutexHolder holder(_lock); - PT(PointerEventList) result = _pointer_events; - _pointer_events = 0; - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::enable_pointer_mode -// Access: Public -// Description: There are two modes: raw mode, and pointer mode. -// In pointer mode, the mouse stops when it reaches the -// edges of the window. In raw mode, the mouse ignores -// the screen boundaries and can continue indefinitely, -// even into negative coordinates. In raw mode, each -// "blip" from the mouse hardware corresponds to a -// change of 1 unit in the mouse's (x,y) coordinate. -// In pointer mode, a variety of speed adjustment factors -// and concepts like "mouse acceleration" may be applied. -// -// Mouse zero represents the system mouse pointer. This -// is by definition a pointer, not a raw mouse. It is -// an error to try to enable or disable pointer mode on -// mouse zero. -//////////////////////////////////////////////////////////////////// -void GraphicsWindowInputDevice:: -enable_pointer_mode(double speed) { - LightMutexHolder holder(_lock); - nassertv(_device_index != 0); - _pointer_mode_enable = true; - _pointer_speed = speed; - _mouse_data._xpos = _host->get_x_size() * 0.5; - _mouse_data._ypos = _host->get_y_size() * 0.5; - _mouse_data._in_window = true; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::disable_pointer_mode -// Access: Public -// Description: see enable_pointer_mode. -//////////////////////////////////////////////////////////////////// -void GraphicsWindowInputDevice:: -disable_pointer_mode() { - LightMutexHolder holder(_lock); - nassertv(_device_index != 0); - _pointer_mode_enable = false; - _pointer_speed = 1.0; - _mouse_data = _true_mouse_data; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::set_pointer -// Access: Published -// Description: Records that a mouse movement has taken place. -//////////////////////////////////////////////////////////////////// -void GraphicsWindowInputDevice:: -set_pointer(bool inwin, double x, double y, double time) { - LightMutexHolder holder(_lock); - - double delta_x = x - _true_mouse_data._xpos; - double delta_y = y - _true_mouse_data._ypos; - _true_mouse_data._in_window = inwin; - _true_mouse_data._xpos = x; - _true_mouse_data._ypos = y; - - if (_pointer_mode_enable) { - double pointer_x = _mouse_data._xpos; - double pointer_y = _mouse_data._ypos; - pointer_x += (delta_x * _pointer_speed); - pointer_y += (delta_y * _pointer_speed); - double xhi = _host->get_x_size(); - double yhi = _host->get_y_size(); - if (pointer_x < 0.0) pointer_x = 0.0; - if (pointer_y < 0.0) pointer_y = 0.0; - if (pointer_x > xhi) pointer_x = xhi; - if (pointer_y > yhi) pointer_y = yhi; - _mouse_data._in_window = true; - _mouse_data._xpos = pointer_x; - _mouse_data._ypos = pointer_y; - } else { - _mouse_data = _true_mouse_data; - } - - if (_enable_pointer_events) { - int seq = _event_sequence++; - if (_pointer_events == 0) { - _pointer_events = new PointerEventList(); - } - _pointer_events->add_event(_mouse_data._in_window, - _mouse_data._xpos, - _mouse_data._ypos, - seq, time); - } -} - //////////////////////////////////////////////////////////////////// // Function: GraphicsWindowInputDevice::button_down // Access: Published @@ -285,7 +110,7 @@ set_pointer(bool inwin, double x, double y, double time) { void GraphicsWindowInputDevice:: button_down(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_down, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_down, time)); _buttons_held.insert(button); } @@ -300,7 +125,7 @@ button_down(ButtonHandle button, double time) { void GraphicsWindowInputDevice:: button_resume_down(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_resume_down, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_resume_down, time)); _buttons_held.insert(button); } @@ -312,7 +137,7 @@ button_resume_down(ButtonHandle button, double time) { void GraphicsWindowInputDevice:: button_up(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_up, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_up, time)); _buttons_held.erase(button); } @@ -325,7 +150,7 @@ button_up(ButtonHandle button, double time) { void GraphicsWindowInputDevice:: keystroke(int keycode, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(keycode, time)); + _button_events->add_event(ButtonEvent(keycode, time)); } //////////////////////////////////////////////////////////////////// @@ -340,7 +165,7 @@ void GraphicsWindowInputDevice:: candidate(const wstring &candidate_string, size_t highlight_start, size_t highlight_end, size_t cursor_pos) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(candidate_string, + _button_events->add_event(ButtonEvent(candidate_string, highlight_start, highlight_end, cursor_pos)); } @@ -361,7 +186,7 @@ focus_lost(double time) { LightMutexHolder holder(_lock); ButtonsHeld::iterator bi; for (bi = _buttons_held.begin(); bi != _buttons_held.end(); ++bi) { - _button_events.push_back(ButtonEvent(*bi, ButtonEvent::T_up, time)); + _button_events->add_event(ButtonEvent(*bi, ButtonEvent::T_up, time)); } _buttons_held.clear(); } @@ -374,7 +199,7 @@ focus_lost(double time) { void GraphicsWindowInputDevice:: raw_button_down(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_raw_down, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_raw_down, time)); } //////////////////////////////////////////////////////////////////// @@ -385,5 +210,5 @@ raw_button_down(ButtonHandle button, double time) { void GraphicsWindowInputDevice:: raw_button_up(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_raw_up, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_raw_up, time)); } diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index e76d48c5bc..56e62ffab0 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -16,139 +16,79 @@ #define GRAPHICSWINDOWINPUTDEVICE_H #include "pandabase.h" - -#include "buttonEvent.h" -#include "pointerEvent.h" -#include "pointerEventList.h" -#include "mouseData.h" -#include "clockObject.h" - -#include "pdeque.h" -#include "pvector.h" -#include "lightMutex.h" -#include "lightMutexHolder.h" +#include "inputDevice.h" // Forward declarations class GraphicsWindow; //////////////////////////////////////////////////////////////////// // Class : GraphicsWindowInputDevice -// Description : This is a structure representing a single input -// device that may be associated with a window. -// Typically this will be a keyboard/mouse pair, and -// there will be exactly one of these associated with -// each window, but other variants are possible. +// Description : This is a virtual input device that represents +// the keyboard and mouse pair that is associated with +// a particular window. It collects mouse and keyboard +// events from the windowing system while the window is +// in focus. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA_DISPLAY GraphicsWindowInputDevice { +class EXPCL_PANDA_DISPLAY GraphicsWindowInputDevice : public InputDevice { private: GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags); public: - static GraphicsWindowInputDevice pointer_only(GraphicsWindow *host, const string &name); - static GraphicsWindowInputDevice keyboard_only(GraphicsWindow *host, const string &name); - static GraphicsWindowInputDevice pointer_and_keyboard(GraphicsWindow *host, const string &name); + static PT(GraphicsWindowInputDevice) pointer_only(GraphicsWindow *host, const string &name); + static PT(GraphicsWindowInputDevice) keyboard_only(GraphicsWindow *host, const string &name); + static PT(GraphicsWindowInputDevice) pointer_and_keyboard(GraphicsWindow *host, const string &name); INLINE GraphicsWindowInputDevice(); GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©); void operator = (const GraphicsWindowInputDevice ©); ~GraphicsWindowInputDevice(); - INLINE string get_name() const; - INLINE bool has_pointer() const; - INLINE bool has_keyboard() const; - INLINE void set_device_index(int index); - INLINE MouseData get_pointer() const; - INLINE MouseData get_raw_pointer() const; - - INLINE void enable_pointer_events(); - INLINE void disable_pointer_events(); - - void enable_pointer_mode(double speed); - void disable_pointer_mode(); - - bool has_button_event() const; - ButtonEvent get_button_event(); - bool has_pointer_event() const; - PT(PointerEventList) get_pointer_events(); - PUBLISHED: // The following interface is for the various kinds of // GraphicsWindows to record the data incoming on the device. - INLINE void button_down(ButtonHandle button); - INLINE void button_resume_down(ButtonHandle button); - INLINE void button_up(ButtonHandle button); - INLINE void keystroke(int keycode); - INLINE void focus_lost(); - INLINE void raw_button_down(ButtonHandle button); - INLINE void raw_button_up(ButtonHandle button); - INLINE void set_pointer_in_window(double x, double y); - INLINE void set_pointer_out_of_window(); + void button_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); + void button_resume_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); + void button_up(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); - void button_down(ButtonHandle button, double time); - void button_resume_down(ButtonHandle button, double time); - void button_up(ButtonHandle button, double time); - void keystroke(int keycode, double time); + void keystroke(int keycode, double time = ClockObject::get_global_clock()->get_frame_time()); void candidate(const wstring &candidate_string, size_t highlight_start, size_t highlight_end, size_t cursor_pos); - void focus_lost(double time); - void raw_button_down(ButtonHandle button, double time); - void raw_button_up(ButtonHandle button, double time); - INLINE void set_pointer_in_window(double x, double y, double time); - INLINE void set_pointer_out_of_window(double time); - void set_pointer(bool inwin, double x, double y, double time); + void focus_lost(double time = ClockObject::get_global_clock()->get_frame_time()); -public: - // We need these methods to make VC++ happy when we try to - // instantiate a pvector. They don't do - // anything useful. - INLINE bool operator == (const GraphicsWindowInputDevice &other) const; - INLINE bool operator != (const GraphicsWindowInputDevice &other) const; - INLINE bool operator < (const GraphicsWindowInputDevice &other) const; + void raw_button_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); + void raw_button_up(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); + + INLINE void set_pointer_in_window(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); private: - enum InputDeviceFlags { - IDF_has_pointer = 0x01, - IDF_has_keyboard = 0x02 - }; - typedef pdeque ButtonEvents; - - LightMutex _lock; - GraphicsWindow *_host; - - string _name; - int _flags; int _device_index; - int _event_sequence; - - bool _pointer_mode_enable; - double _pointer_speed; - - bool _enable_pointer_events; - MouseData _mouse_data; - MouseData _true_mouse_data; - ButtonEvents _button_events; - PT(PointerEventList) _pointer_events; typedef pset ButtonsHeld; ButtonsHeld _buttons_held; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + InputDevice::init_type(); + register_type(_type_handle, "GraphicsWindowInputDevice", + InputDevice::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; }; #include "graphicsWindowInputDevice.I" -#define EXPCL EXPCL_PANDA_DISPLAY -#define EXPTP EXPTP_PANDA_DISPLAY -#define TYPE GraphicsWindowInputDevice -#define NAME vector_GraphicsWindowInputDevice - -#include "vector_src.h" - -// Tell GCC that we'll take care of the instantiation explicitly here. -#ifdef __GNUC__ -#pragma interface -#endif - #endif diff --git a/panda/src/device/mouseAndKeyboard.cxx b/panda/src/display/mouseAndKeyboard.cxx similarity index 88% rename from panda/src/device/mouseAndKeyboard.cxx rename to panda/src/display/mouseAndKeyboard.cxx index e8c102746e..7666b63404 100644 --- a/panda/src/device/mouseAndKeyboard.cxx +++ b/panda/src/display/mouseAndKeyboard.cxx @@ -41,7 +41,6 @@ MouseAndKeyboard(GraphicsWindow *window, int device, const string &name) : _pixel_xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); _pixel_size = new EventStoreVec2(LPoint2(0.0f, 0.0f)); _xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); - _button_events = new ButtonEventList; } //////////////////////////////////////////////////////////////////// @@ -93,20 +92,18 @@ get_source_device() const { void MouseAndKeyboard:: do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { - if (_window->has_button_event(_device)) { - // Fill up the button events. - _button_events->clear(); - while (_window->has_button_event(_device)) { - ButtonEvent be = _window->get_button_event(_device); - _button_events->add_event(be); - } - output.set_data(_button_events_output, EventParameter(_button_events)); + + PT(InputDevice) device = _window->get_input_device(_device); + + if (device->has_button_event()) { + PT(ButtonEventList) bel = device->get_button_events(); + output.set_data(_button_events_output, EventParameter(bel)); } - if (_window->has_pointer_event(_device)) { - PT(PointerEventList) pel = _window->get_pointer_events(_device); + if (device->has_pointer_event()) { + PT(PointerEventList) pel = device->get_pointer_events(); output.set_data(_pointer_events_output, EventParameter(pel)); } - + // Get the window size. WindowProperties properties = _window->get_properties(); if (properties.has_size()) { @@ -116,18 +113,18 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, _pixel_size->set_value(LPoint2(w, h)); output.set_data(_pixel_size_output, EventParameter(_pixel_size)); - if (_window->has_pointer(_device)) { - const MouseData &mdata = _window->get_pointer(_device); + if (device->has_pointer()) { + const MouseData &mdata = device->get_pointer(); if (mdata._in_window) { // Get mouse motion in pixels. _pixel_xy->set_value(LPoint2(mdata._xpos, mdata._ypos)); output.set_data(_pixel_xy_output, EventParameter(_pixel_xy)); - + // Normalize pixel motion to range [-1,1]. PN_stdfloat xf = (PN_stdfloat)(2 * mdata._xpos) / (PN_stdfloat)w - 1.0f; PN_stdfloat yf = 1.0f - (PN_stdfloat)(2 * mdata._ypos) / (PN_stdfloat)h; - + _xy->set_value(LPoint2(xf, yf)); output.set_data(_xy_output, EventParameter(_xy)); } diff --git a/panda/src/device/mouseAndKeyboard.h b/panda/src/display/mouseAndKeyboard.h similarity index 98% rename from panda/src/device/mouseAndKeyboard.h rename to panda/src/display/mouseAndKeyboard.h index 939a04aa8a..f39674900a 100644 --- a/panda/src/device/mouseAndKeyboard.h +++ b/panda/src/display/mouseAndKeyboard.h @@ -51,7 +51,7 @@ PUBLISHED: PT(GraphicsWindow) get_source_window() const; int get_source_device() const; - + protected: // Inherited from DataNode virtual void do_transmit_data(DataGraphTraverser *trav, @@ -69,7 +69,6 @@ private: PT(EventStoreVec2) _pixel_xy; PT(EventStoreVec2) _pixel_size; PT(EventStoreVec2) _xy; - PT(ButtonEventList) _button_events; PT(GraphicsWindow) _window; int _device; diff --git a/panda/src/display/p3display_composite2.cxx b/panda/src/display/p3display_composite2.cxx index b3ba58995b..c2d47f7aad 100644 --- a/panda/src/display/p3display_composite2.cxx +++ b/panda/src/display/p3display_composite2.cxx @@ -5,6 +5,7 @@ #include "graphicsWindowProc.cxx" #include "graphicsWindowProcCallbackData.cxx" #include "graphicsWindowInputDevice.cxx" +#include "mouseAndKeyboard.cxx" #include "nativeWindowHandle.cxx" #include "parasiteBuffer.cxx" #include "standardMunger.cxx" diff --git a/panda/src/putil/config_util.cxx b/panda/src/putil/config_util.cxx index 44ac7bd6a4..8610341402 100644 --- a/panda/src/putil/config_util.cxx +++ b/panda/src/putil/config_util.cxx @@ -32,6 +32,7 @@ #include "datagram.h" #include "doubleBitMask.h" #include "factoryParam.h" +#include "gamepadButton.h" #include "namable.h" #include "nodeCachedReferenceCount.h" #include "paramValue.h" @@ -225,6 +226,7 @@ init_libputil() { WritableConfigurable::init_type(); WritableParam::init_type(); + GamepadButton::init_gamepad_buttons(); KeyboardButton::init_keyboard_buttons(); MouseButton::init_mouse_buttons(); diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx new file mode 100644 index 0000000000..ed8d41a435 --- /dev/null +++ b/panda/src/putil/gamepadButton.cxx @@ -0,0 +1,75 @@ +// Filename: gamepadButton.cxx +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "gamepadButton.h" +#include "buttonRegistry.h" + +#define DEFINE_GAMEPAD_BUTTON_HANDLE(KeyName) \ + static ButtonHandle _##KeyName; \ + ButtonHandle GamepadButton::KeyName() { return _##KeyName; } + +DEFINE_GAMEPAD_BUTTON_HANDLE(lstick) +DEFINE_GAMEPAD_BUTTON_HANDLE(rstick) +DEFINE_GAMEPAD_BUTTON_HANDLE(lshoulder) +DEFINE_GAMEPAD_BUTTON_HANDLE(rshoulder) +DEFINE_GAMEPAD_BUTTON_HANDLE(ltrigger) +DEFINE_GAMEPAD_BUTTON_HANDLE(rtrigger) + +DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_left) +DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_right) +DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_up) +DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_down) + +DEFINE_GAMEPAD_BUTTON_HANDLE(back) +DEFINE_GAMEPAD_BUTTON_HANDLE(guide) +DEFINE_GAMEPAD_BUTTON_HANDLE(start) + +DEFINE_GAMEPAD_BUTTON_HANDLE(action_a) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_b) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_c) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_x) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_y) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_z) + +//////////////////////////////////////////////////////////////////// +// Function: GamepadButton::init_gamepad_buttons +// Access: Public, Static +// Description: This is intended to be called only once, by the +// static initialization performed in config_util.cxx. +//////////////////////////////////////////////////////////////////// +void GamepadButton:: +init_gamepad_buttons() { + ButtonRegistry::ptr()->register_button(_lstick, "lstick"); + ButtonRegistry::ptr()->register_button(_rstick, "rstick"); + ButtonRegistry::ptr()->register_button(_lshoulder, "lshoulder"); + ButtonRegistry::ptr()->register_button(_rshoulder, "rshoulder"); + ButtonRegistry::ptr()->register_button(_ltrigger, "ltrigger"); + ButtonRegistry::ptr()->register_button(_rtrigger, "rtrigger"); + + ButtonRegistry::ptr()->register_button(_dpad_left, "dpad_left"); + ButtonRegistry::ptr()->register_button(_dpad_right, "dpad_right"); + ButtonRegistry::ptr()->register_button(_dpad_up, "dpad_up"); + ButtonRegistry::ptr()->register_button(_dpad_down, "dpad_down"); + + ButtonRegistry::ptr()->register_button(_back, "back"); + ButtonRegistry::ptr()->register_button(_guide, "guide"); + ButtonRegistry::ptr()->register_button(_start, "start"); + + ButtonRegistry::ptr()->register_button(_action_a, "action_a"); + ButtonRegistry::ptr()->register_button(_action_b, "action_b"); + ButtonRegistry::ptr()->register_button(_action_c, "action_c"); + ButtonRegistry::ptr()->register_button(_action_x, "action_x"); + ButtonRegistry::ptr()->register_button(_action_y, "action_y"); + ButtonRegistry::ptr()->register_button(_action_z, "action_z"); +} diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h new file mode 100644 index 0000000000..4d6b7afa88 --- /dev/null +++ b/panda/src/putil/gamepadButton.h @@ -0,0 +1,57 @@ +// Filename: gamepadButton.h +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef GAMEPADBUTTON_H +#define GAMEPADBUTTON_H + +#include "pandabase.h" + +#include "buttonHandle.h" + +//////////////////////////////////////////////////////////////////// +// Class : GamepadButton +// Description : This class is just used as a convenient namespace for +// grouping all of these handy functions that return +// buttons which map to gamepad buttons. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_PUTIL GamepadButton { +PUBLISHED: + static ButtonHandle lstick(); + static ButtonHandle rstick(); + static ButtonHandle lshoulder(); + static ButtonHandle rshoulder(); + static ButtonHandle ltrigger(); + static ButtonHandle rtrigger(); + + static ButtonHandle dpad_left(); + static ButtonHandle dpad_right(); + static ButtonHandle dpad_up(); + static ButtonHandle dpad_down(); + + static ButtonHandle back(); + static ButtonHandle guide(); + static ButtonHandle start(); + + static ButtonHandle action_a(); + static ButtonHandle action_b(); + static ButtonHandle action_c(); + static ButtonHandle action_x(); + static ButtonHandle action_y(); + static ButtonHandle action_z(); + +public: + static void init_gamepad_buttons(); +}; + +#endif diff --git a/panda/src/putil/p3putil_composite1.cxx b/panda/src/putil/p3putil_composite1.cxx index 3fd7673773..294f465629 100644 --- a/panda/src/putil/p3putil_composite1.cxx +++ b/panda/src/putil/p3putil_composite1.cxx @@ -28,4 +28,3 @@ #include "factoryBase.cxx" #include "factoryParam.cxx" #include "factoryParams.cxx" -#include "globalPointerRegistry.cxx" diff --git a/panda/src/putil/p3putil_composite2.cxx b/panda/src/putil/p3putil_composite2.cxx index a4ce5c9cfc..9b0f01d851 100644 --- a/panda/src/putil/p3putil_composite2.cxx +++ b/panda/src/putil/p3putil_composite2.cxx @@ -1,3 +1,5 @@ +#include "gamepadButton.cxx" +#include "globalPointerRegistry.cxx" #include "ioPtaDatagramFloat.cxx" #include "ioPtaDatagramInt.cxx" #include "ioPtaDatagramShort.cxx" diff --git a/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm b/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm index 876de6450d..f14641becf 100644 --- a/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm +++ b/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm @@ -419,7 +419,7 @@ OSStatus TinyOsxGraphicsWindow::handleTextInput (EventHandlerCallRef myHandler, } for (unsigned int x = 0; x < actualSize/sizeof(UniChar); ++x) { - _input_devices[0].keystroke(text[x]); + _input_devices[0]->keystroke(text[x]); } DisposePtr((char *)text); } @@ -492,10 +492,10 @@ TinyOsxGraphicsWindow::TinyOsxGraphicsWindow(GraphicsEngine *engine, GraphicsPip _current_icon(NULL), _ID(id_seed++), _originalMode(NULL) { - GraphicsWindowInputDevice device = + PT(InputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard/mouse"); _input_devices.push_back(device); - _input_devices[0].set_pointer_in_window(0, 0); + device->set_pointer_in_window(0, 0); _last_key_modifiers = 0; _last_buttons = 0; @@ -579,7 +579,7 @@ bool TinyOsxGraphicsWindow::set_icon_filename(const Filename &icon_filename) { //////////////////////////////////////////////////////////////////// void TinyOsxGraphicsWindow:: set_pointer_in_window(int x, int y) { - _input_devices[0].set_pointer_in_window(x, y); + _input_devices[0]->set_pointer_in_window(x, y); if (_cursor_hidden != _display_hide_cursor) { if (_cursor_hidden) { @@ -600,7 +600,7 @@ set_pointer_in_window(int x, int y) { //////////////////////////////////////////////////////////////////// void TinyOsxGraphicsWindow:: set_pointer_out_of_window() { - _input_devices[0].set_pointer_out_of_window(); + _input_devices[0]->set_pointer_out_of_window(); if (_display_hide_cursor) { CGDisplayShowCursor(kCGDirectMainDisplay); @@ -1333,13 +1333,13 @@ void TinyOsxGraphicsWindow::SystemPointToLocalPoint(Point &qdGlobalPoint) { _wheel_delta += this_wheel_delta; SInt32 wheel_scale = osx_mouse_wheel_scale; while (_wheel_delta > wheel_scale) { - _input_devices[0].button_down(MouseButton::wheel_up()); - _input_devices[0].button_up(MouseButton::wheel_up()); + _input_devices[0]->button_down(MouseButton::wheel_up()); + _input_devices[0]->button_up(MouseButton::wheel_up()); _wheel_delta -= wheel_scale; } while (_wheel_delta < -wheel_scale) { - _input_devices[0].button_down(MouseButton::wheel_down()); - _input_devices[0].button_up(MouseButton::wheel_down()); + _input_devices[0]->button_down(MouseButton::wheel_down()); + _input_devices[0]->button_up(MouseButton::wheel_down()); _wheel_delta += wheel_scale; } } @@ -1519,25 +1519,25 @@ HandleButtonDelta(UInt32 new_buttons) { if (changed & 0x01) { if (new_buttons & 0x01) { - _input_devices[0].button_down(MouseButton::one()); + _input_devices[0]->button_down(MouseButton::one()); } else { - _input_devices[0].button_up(MouseButton::one()); + _input_devices[0]->button_up(MouseButton::one()); } } if (changed & 0x04) { if (new_buttons & 0x04) { - _input_devices[0].button_down(MouseButton::two()); + _input_devices[0]->button_down(MouseButton::two()); } else { - _input_devices[0].button_up(MouseButton::two()); + _input_devices[0]->button_up(MouseButton::two()); } } if (changed & 0x02) { if (new_buttons & 0x02) { - _input_devices[0].button_down(MouseButton::three()); + _input_devices[0]->button_down(MouseButton::three()); } else { - _input_devices[0].button_up(MouseButton::three()); + _input_devices[0]->button_up(MouseButton::three()); } } @@ -1745,7 +1745,7 @@ void TinyOsxGraphicsWindow::set_properties_now(WindowProperties &properties) { if (properties.has_cursor_hidden()) { _properties.set_cursor_hidden(properties.get_cursor_hidden()); _cursor_hidden = properties.get_cursor_hidden(); - if (_cursor_hidden && _input_devices[0].has_pointer()) { + if (_cursor_hidden && _input_devices[0]->has_pointer()) { if (!_display_hide_cursor) { CGDisplayHideCursor(kCGDirectMainDisplay); _display_hide_cursor = true; diff --git a/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx b/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx index 47631c6321..2da9337c1f 100644 --- a/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx +++ b/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx @@ -46,9 +46,7 @@ TinySDLGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _pitch = 0; update_pixel_factor(); - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); - add_input_device(device); + add_input_device(GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse")); } //////////////////////////////////////////////////////////////////// @@ -184,35 +182,35 @@ process_events() { switch(evt.type) { case SDL_KEYDOWN: if (evt.key.keysym.unicode) { - _input_devices[0].keystroke(evt.key.keysym.unicode); + _input_devices[0]->keystroke(evt.key.keysym.unicode); } button = get_keyboard_button(evt.key.keysym.sym); if (button != ButtonHandle::none()) { - _input_devices[0].button_down(button); + _input_devices[0]->button_down(button); } break; case SDL_KEYUP: button = get_keyboard_button(evt.key.keysym.sym); if (button != ButtonHandle::none()) { - _input_devices[0].button_up(button); + _input_devices[0]->button_up(button); } break; case SDL_MOUSEBUTTONDOWN: button = get_mouse_button(evt.button.button); - _input_devices[0].set_pointer_in_window(evt.button.x, evt.button.y); - _input_devices[0].button_down(button); + _input_devices[0]->set_pointer_in_window(evt.button.x, evt.button.y); + _input_devices[0]->button_down(button); break; case SDL_MOUSEBUTTONUP: button = get_mouse_button(evt.button.button); - _input_devices[0].set_pointer_in_window(evt.button.x, evt.button.y); - _input_devices[0].button_up(button); + _input_devices[0]->set_pointer_in_window(evt.button.x, evt.button.y); + _input_devices[0]->button_up(button); break; case SDL_MOUSEMOTION: - _input_devices[0].set_pointer_in_window(evt.motion.x, evt.motion.y); + _input_devices[0]->set_pointer_in_window(evt.motion.x, evt.motion.y); break; case SDL_VIDEORESIZE: diff --git a/panda/src/tinydisplay/tinyXGraphicsWindow.cxx b/panda/src/tinydisplay/tinyXGraphicsWindow.cxx index 924ababedb..0b6cf0b48e 100644 --- a/panda/src/tinydisplay/tinyXGraphicsWindow.cxx +++ b/panda/src/tinydisplay/tinyXGraphicsWindow.cxx @@ -215,9 +215,7 @@ process_events() { if (_xwindow == (X11_Window)0) { return; } - - poll_raw_mice(); - + XEvent event; XKeyEvent keyrelease_event; bool got_keyrelease_event = false; @@ -294,18 +292,18 @@ process_events() { case ButtonPress: // This refers to the mouse buttons. button = get_mouse_button(event.xbutton); - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); - _input_devices[0].button_down(button); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->button_down(button); break; case ButtonRelease: button = get_mouse_button(event.xbutton); - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); - _input_devices[0].button_up(button); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->button_up(button); break; case MotionNotify: - _input_devices[0].set_pointer_in_window(event.xmotion.x, event.xmotion.y); + _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y); break; case KeyPress: @@ -322,11 +320,11 @@ process_events() { break; case EnterNotify: - _input_devices[0].set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); + _input->set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); break; case LeaveNotify: - _input_devices[0].set_pointer_out_of_window(); + _input->set_pointer_out_of_window(); break; case FocusIn: @@ -335,7 +333,7 @@ process_events() { break; case FocusOut: - _input_devices[0].focus_lost(); + _input->focus_lost(); properties.set_foreground(false); system_changed_properties(properties); break; diff --git a/panda/src/vrpn/vrpnAnalog.cxx b/panda/src/vrpn/vrpnAnalog.cxx index 40e55796a4..377574b6fe 100644 --- a/panda/src/vrpn/vrpnAnalog.cxx +++ b/panda/src/vrpn/vrpnAnalog.cxx @@ -115,7 +115,6 @@ vrpn_analog_callback(void *userdata, const vrpn_ANALOGCB info) { Devices::iterator di; for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnAnalogDevice *device = (*di); - device->acquire(); for (int i = 0; i < info.num_channel; i++) { if (vrpn_cat.is_debug()) { if (device->get_control_state(i) != info.channel[i]) { @@ -125,6 +124,5 @@ vrpn_analog_callback(void *userdata, const vrpn_ANALOGCB info) { } device->set_control_state(i, info.channel[i]); } - device->unlock(); } } diff --git a/panda/src/vrpn/vrpnButton.cxx b/panda/src/vrpn/vrpnButton.cxx index 444fea8708..d6bca1cf4b 100644 --- a/panda/src/vrpn/vrpnButton.cxx +++ b/panda/src/vrpn/vrpnButton.cxx @@ -119,8 +119,6 @@ vrpn_button_callback(void *userdata, const vrpn_BUTTONCB info) { Devices::iterator di; for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnButtonDevice *device = (*di); - device->acquire(); device->set_button_state(info.button, info.state != 0); - device->unlock(); } } diff --git a/panda/src/vrpn/vrpnDial.cxx b/panda/src/vrpn/vrpnDial.cxx index 34d4ae549a..0c958d98ab 100644 --- a/panda/src/vrpn/vrpnDial.cxx +++ b/panda/src/vrpn/vrpnDial.cxx @@ -120,8 +120,6 @@ vrpn_dial_callback(void *userdata, const vrpn_DIALCB info) { Devices::iterator di; for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnDialDevice *device = (*di); - device->acquire(); device->push_dial(info.dial, info.change); - device->unlock(); } } diff --git a/panda/src/vrpn/vrpnTracker.cxx b/panda/src/vrpn/vrpnTracker.cxx index c99e95b513..37cc8c03b8 100644 --- a/panda/src/vrpn/vrpnTracker.cxx +++ b/panda/src/vrpn/vrpnTracker.cxx @@ -123,11 +123,10 @@ vrpn_position_callback(void *userdata, const vrpn_TRACKERCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_position) { - device->acquire(); - device->_data.set_time(VrpnClient::convert_to_secs(info.msg_time)); - device->_data.set_pos(LPoint3(info.pos[0], info.pos[1], info.pos[2])); - device->_data.set_orient(LOrientation(info.quat[3], info.quat[0], info.quat[1], info.quat[2])); - device->unlock(); + device->set_tracker(LPoint3(info.pos[0], info.pos[1], info.pos[2]), + LOrientation(info.quat[3], info.quat[0], + info.quat[1], info.quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } @@ -152,13 +151,10 @@ vrpn_velocity_callback(void *userdata, const vrpn_TRACKERVELCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_velocity) { - device->acquire(); - device->_data.set_time(VrpnClient::convert_to_secs(info.msg_time)); - device->_data.set_pos(LPoint3(info.vel[0], info.vel[1], info.vel[2])); - device->_data.set_orient(LOrientation(info.vel_quat[3], info.vel_quat[0], - info.vel_quat[1], info.vel_quat[2])); - device->_data.set_dt(info.vel_quat_dt); - device->unlock(); + device->set_tracker(LPoint3(info.vel[0], info.vel[1], info.vel[2]), + LOrientation(info.vel_quat[3], info.vel_quat[0], + info.vel_quat[1], info.vel_quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } @@ -183,13 +179,10 @@ vrpn_acceleration_callback(void *userdata, const vrpn_TRACKERACCCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_acceleration) { - device->acquire(); - device->_data.set_time(VrpnClient::convert_to_secs(info.msg_time)); - device->_data.set_pos(LPoint3(info.acc[0], info.acc[1], info.acc[2])); - device->_data.set_orient(LOrientation(info.acc_quat[3], info.acc_quat[0], - info.acc_quat[1], info.acc_quat[2])); - device->_data.set_dt(info.acc_quat_dt); - device->unlock(); + device->set_tracker(LPoint3(info.acc[0], info.acc[1], info.acc[2]), + LOrientation(info.acc_quat[3], info.acc_quat[0], + info.acc_quat[1], info.acc_quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index f270a70f61..4e04206426 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -2786,9 +2786,9 @@ handle_raw_input(HRAWINPUT hraw) { if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { _input_devices[i].set_pointer_in_window(adjx, adjy); } else { - int oldx = _input_devices[i].get_raw_pointer().get_x(); - int oldy = _input_devices[i].get_raw_pointer().get_y(); - _input_devices[i].set_pointer_in_window(oldx + adjx, oldy + adjy); + //int oldx = _input_devices[i].get_raw_pointer().get_x(); + //int oldy = _input_devices[i].get_raw_pointer().get_y(); + //_input_devices[i].set_pointer_in_window(oldx + adjx, oldy + adjy); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) { diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 326953d0c4..8e9f6252bb 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -28,14 +28,10 @@ #include "nativeWindowHandle.h" #include "virtualFileSystem.h" #include "get_x11.h" +#include "evdevInputDevice.h" -#include -#include #include - -#ifdef PHAVE_LINUX_INPUT_H -#include -#endif +#include #ifdef HAVE_XCURSOR static int xcursor_read(XcursorFile *file, unsigned char *buf, int len) { @@ -69,8 +65,6 @@ static int xcursor_seek(XcursorFile *file, long offset, int whence) { TypeHandle x11GraphicsWindow::_type_handle; -#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7))) - //////////////////////////////////////////////////////////////////// // Function: x11GraphicsWindow::Constructor // Access: Public @@ -107,9 +101,9 @@ x11GraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _override_redirect = False; _wm_delete_window = x11_pipe->_wm_delete_window; - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); add_input_device(device); + _input = device; } //////////////////////////////////////////////////////////////////// @@ -143,26 +137,26 @@ move_pointer(int device, int x, int y) { if (device == 0) { // Move the system mouse pointer. if (!_properties.get_foreground() || - !_input_devices[0].get_pointer().get_in_window()) { + !_input->get_pointer().get_in_window()) { // If the window doesn't have input focus, or the mouse isn't // currently within the window, forget it. return false; } - const MouseData &md = _input_devices[0].get_pointer(); + const MouseData &md = _input->get_pointer(); if (!md.get_in_window() || md.get_x() != x || md.get_y() != y) { if (!_dga_mouse_enabled) { XWarpPointer(_display, None, _xwindow, 0, 0, 0, 0, x, y); } - _input_devices[0].set_pointer_in_window(x, y); + _input->set_pointer_in_window(x, y); } return true; } else { // Move a raw mouse. - if ((device < 1)||(device >= _input_devices.size())) { + if (device < 1 || device >= _input_devices.size()) { return false; } - _input_devices[device].set_pointer_in_window(x, y); + //_input_devices[device]->set_pointer_in_window(x, y); return true; } } @@ -249,8 +243,6 @@ process_events() { return; } - poll_raw_mice(); - XEvent event; XKeyEvent keyrelease_event; bool got_keyrelease_event = false; @@ -311,25 +303,25 @@ process_events() { // This refers to the mouse buttons. button = get_mouse_button(event.xbutton); if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); } - _input_devices[0].button_down(button); + _input->button_down(button); break; case ButtonRelease: button = get_mouse_button(event.xbutton); if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); } - _input_devices[0].button_up(button); + _input->button_up(button); break; case MotionNotify: if (_dga_mouse_enabled) { - const MouseData &md = _input_devices[0].get_raw_pointer(); - _input_devices[0].set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root); + const MouseData &md = _input->get_pointer(); + _input->set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root); } else { - _input_devices[0].set_pointer_in_window(event.xmotion.x, event.xmotion.y); + _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y); } break; @@ -348,15 +340,15 @@ process_events() { case EnterNotify: if (_dga_mouse_enabled) { - const MouseData &md = _input_devices[0].get_raw_pointer(); - _input_devices[0].set_pointer_in_window(md.get_x(), md.get_y()); + const MouseData &md = _input->get_pointer(); + _input->set_pointer_in_window(md.get_x(), md.get_y()); } else { - _input_devices[0].set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); + _input->set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); } break; case LeaveNotify: - _input_devices[0].set_pointer_out_of_window(); + _input->set_pointer_out_of_window(); break; case FocusIn: @@ -365,7 +357,7 @@ process_events() { break; case FocusOut: - _input_devices[0].focus_lost(); + _input->focus_lost(); properties.set_foreground(false); changed_properties = true; break; @@ -751,7 +743,7 @@ set_properties_now(WindowProperties &properties) { XQueryPointer(_display, _xwindow, &event.xbutton.root, &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state); - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); } } else { x11display_cat.info() << "XF86DGA extension not available\n"; @@ -1295,66 +1287,43 @@ open_raw_mice() { bool any_mice = false; for (int i=0; i<64; i++) { - uint8_t evtypes[EV_MAX/8 + 1]; ostringstream fnb; fnb << "/dev/input/event" << i; string fn = fnb.str(); int fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK, 0); if (fd >= 0) { - any_present = true; - char name[256]; - char phys[256]; - char uniq[256]; - if ((ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0)|| - (ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys) < 0)|| - (ioctl(fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0)|| - (ioctl(fd, EVIOCGBIT(0, EV_MAX), &evtypes) < 0)) { - close(fd); - x11display_cat.error() << - "Opening raw mice: ioctl failed on " << fn << "\n"; - } else { - if (test_bit(EV_REL, evtypes) || test_bit(EV_ABS, evtypes)) { - for (char *p=name; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } - } - for (char *p=uniq; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } - } - string full_id = ((string)name) + "." + uniq; - MouseDeviceInfo inf; - inf._fd = fd; - inf._input_device_index = _input_devices.size(); - inf._io_buffer = ""; - _mouse_device_info.push_back(inf); - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_only(this, full_id); - add_input_device(device); - x11display_cat.info() << "Raw mouse " << - inf._input_device_index << " detected: " << full_id << "\n"; - any_mice = true; - } else { - close(fd); - } + EvdevInputDevice *device = new EvdevInputDevice(fd); + nassertd(device != NULL) continue; + + if (device->has_pointer()) { + add_input_device(device); + + x11display_cat.info() + << "Raw mouse " << _input_devices.size() + << " detected: " << device->get_name() << "\n"; + + any_mice = true; + any_present = true; } } else { - if ((errno == ENOENT)||(errno == ENOTDIR)) { + if (errno == ENOENT || errno == ENOTDIR) { break; } else { any_present = true; - x11display_cat.error() << - "Opening raw mice: " << strerror(errno) << " " << fn << "\n"; + x11display_cat.error() + << "Opening raw mice: " << strerror(errno) << " " << fn << "\n"; } } } - if (!any_present) { + if (any_mice) { + _properties.set_raw_mice(true); + + } else if (!any_present) { x11display_cat.error() << "Opening raw mice: files not found: /dev/input/event*\n"; - } else if (!any_mice) { + + } else { x11display_cat.error() << "Opening raw mice: no mouse devices detected in /dev/input/event*\n"; } @@ -1364,69 +1333,6 @@ open_raw_mice() { #endif } -//////////////////////////////////////////////////////////////////// -// Function: x11GraphicsWindow::poll_raw_mice -// Access: Private -// Description: Reads events from the raw mouse device files. -//////////////////////////////////////////////////////////////////// -void x11GraphicsWindow:: -poll_raw_mice() { -#ifdef PHAVE_LINUX_INPUT_H - for (int di = 0; di < _mouse_device_info.size(); ++di) { - MouseDeviceInfo &inf = _mouse_device_info[di]; - - // Read all bytes into buffer. - if (inf._fd >= 0) { - while (1) { - char tbuf[1024]; - int nread = read(inf._fd, tbuf, sizeof(tbuf)); - if (nread > 0) { - inf._io_buffer += string(tbuf, nread); - } else { - if ((nread < 0) && ((errno == EWOULDBLOCK) || (errno==EAGAIN))) { - break; - } - close(inf._fd); - inf._fd = -1; - break; - } - } - } - - // Process events. - int nevents = inf._io_buffer.size() / sizeof(struct input_event); - if (nevents == 0) { - continue; - } - const input_event *events = (const input_event *)(inf._io_buffer.c_str()); - GraphicsWindowInputDevice &dev = _input_devices[inf._input_device_index]; - int x = dev.get_raw_pointer().get_x(); - int y = dev.get_raw_pointer().get_y(); - for (int i = 0; i < nevents; i++) { - if (events[i].type == EV_REL) { - if (events[i].code == REL_X) x += events[i].value; - if (events[i].code == REL_Y) y += events[i].value; - } else if (events[i].type == EV_ABS) { - if (events[i].code == ABS_X) x = events[i].value; - if (events[i].code == ABS_Y) y = events[i].value; - } else if (events[i].type == EV_KEY) { - if ((events[i].code >= BTN_MOUSE) && (events[i].code < BTN_MOUSE + 8)) { - int btn = events[i].code - BTN_MOUSE; - dev.set_pointer_in_window(x, y); - if (events[i].value) { - dev.button_down(MouseButton::button(btn)); - } else { - dev.button_up(MouseButton::button(btn)); - } - } - } - } - inf._io_buffer.erase(0, nevents * sizeof(struct input_event)); - dev.set_pointer_in_window(x, y); - } -#endif -} - //////////////////////////////////////////////////////////////////// // Function: x11GraphicsWindow::handle_keystroke // Access: Private @@ -1436,7 +1342,7 @@ poll_raw_mice() { void x11GraphicsWindow:: handle_keystroke(XKeyEvent &event) { if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.x, event.y); + _input->set_pointer_in_window(event.x, event.y); } if (_ic) { @@ -1454,14 +1360,14 @@ handle_keystroke(XKeyEvent &event) { // Now each of the returned wide characters represents a // keystroke. for (int i = 0; i < len; i++) { - _input_devices[0].keystroke(buffer[i]); + _input->keystroke(buffer[i]); } } else { // Without an input context, just get the ascii keypress. ButtonHandle button = get_button(event, true); if (button.has_ascii_equivalent()) { - _input_devices[0].keystroke(button.get_ascii_equivalent()); + _input->keystroke(button.get_ascii_equivalent()); } } } @@ -1475,30 +1381,32 @@ handle_keystroke(XKeyEvent &event) { void x11GraphicsWindow:: handle_keypress(XKeyEvent &event) { if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.x, event.y); + _input->set_pointer_in_window(event.x, event.y); } // Now get the raw unshifted button. ButtonHandle button = get_button(event, false); if (button != ButtonHandle::none()) { if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) { - _input_devices[0].button_down(KeyboardButton::control()); + _input->button_down(KeyboardButton::control()); } if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) { - _input_devices[0].button_down(KeyboardButton::shift()); + _input->button_down(KeyboardButton::shift()); } if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) { - _input_devices[0].button_down(KeyboardButton::alt()); + _input->button_down(KeyboardButton::alt()); } if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) { - _input_devices[0].button_down(KeyboardButton::meta()); + _input->button_down(KeyboardButton::meta()); } - _input_devices[0].button_down(button); + _input->button_down(button); } - ButtonHandle raw_button = map_raw_button(event.keycode); - if (raw_button != ButtonHandle::none()) { - _input_devices[0].raw_button_down(raw_button); + if (event.keycode >= 9 && event.keycode <= 135) { + ButtonHandle raw_button = map_raw_button(event.keycode); + if (raw_button != ButtonHandle::none()) { + _input->raw_button_down(raw_button); + } } } @@ -1511,30 +1419,32 @@ handle_keypress(XKeyEvent &event) { void x11GraphicsWindow:: handle_keyrelease(XKeyEvent &event) { if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.x, event.y); + _input->set_pointer_in_window(event.x, event.y); } // Now get the raw unshifted button. ButtonHandle button = get_button(event, false); if (button != ButtonHandle::none()) { if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) { - _input_devices[0].button_up(KeyboardButton::control()); + _input->button_up(KeyboardButton::control()); } if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) { - _input_devices[0].button_up(KeyboardButton::shift()); + _input->button_up(KeyboardButton::shift()); } if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) { - _input_devices[0].button_up(KeyboardButton::alt()); + _input->button_up(KeyboardButton::alt()); } if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) { - _input_devices[0].button_up(KeyboardButton::meta()); + _input->button_up(KeyboardButton::meta()); } - _input_devices[0].button_up(button); + _input->button_up(button); } - ButtonHandle raw_button = map_raw_button(event.keycode); - if (raw_button != ButtonHandle::none()) { - _input_devices[0].raw_button_up(raw_button); + if (event.keycode >= 9 && event.keycode <= 135) { + ButtonHandle raw_button = map_raw_button(event.keycode); + if (raw_button != ButtonHandle::none()) { + _input->raw_button_up(raw_button); + } } } @@ -1617,7 +1527,7 @@ get_button(XKeyEvent &key_event, bool allow_shift) { // can do this in just the ASCII set, because we handle // international keyboards elsewhere (via an input context). if ((key_event.state & (ShiftMask | LockMask)) != 0) { - if (key >= XK_a and key <= XK_z) { + if (key >= XK_a && key <= XK_z) { key += (XK_A - XK_a); } } @@ -1962,117 +1872,16 @@ map_button(KeySym key) const { //////////////////////////////////////////////////////////////////// ButtonHandle x11GraphicsWindow:: map_raw_button(KeyCode key) const { - switch (key) { - case 9: return KeyboardButton::escape(); - case 10: return KeyboardButton::ascii_key('1'); - case 11: return KeyboardButton::ascii_key('2'); - case 12: return KeyboardButton::ascii_key('3'); - case 13: return KeyboardButton::ascii_key('4'); - case 14: return KeyboardButton::ascii_key('5'); - case 15: return KeyboardButton::ascii_key('6'); - case 16: return KeyboardButton::ascii_key('7'); - case 17: return KeyboardButton::ascii_key('8'); - case 18: return KeyboardButton::ascii_key('9'); - case 19: return KeyboardButton::ascii_key('0'); - case 20: return KeyboardButton::ascii_key('-'); - case 21: return KeyboardButton::ascii_key('='); - case 22: return KeyboardButton::backspace(); - case 23: return KeyboardButton::tab(); - case 24: return KeyboardButton::ascii_key('q'); - case 25: return KeyboardButton::ascii_key('w'); - case 26: return KeyboardButton::ascii_key('e'); - case 27: return KeyboardButton::ascii_key('r'); - case 28: return KeyboardButton::ascii_key('t'); - case 29: return KeyboardButton::ascii_key('y'); - case 30: return KeyboardButton::ascii_key('u'); - case 31: return KeyboardButton::ascii_key('i'); - case 32: return KeyboardButton::ascii_key('o'); - case 33: return KeyboardButton::ascii_key('p'); - case 34: return KeyboardButton::ascii_key('['); - case 35: return KeyboardButton::ascii_key(']'); - case 36: return KeyboardButton::enter(); - case 37: return KeyboardButton::lcontrol(); - case 38: return KeyboardButton::ascii_key('a'); - case 39: return KeyboardButton::ascii_key('s'); - case 40: return KeyboardButton::ascii_key('d'); - case 41: return KeyboardButton::ascii_key('f'); - case 42: return KeyboardButton::ascii_key('g'); - case 43: return KeyboardButton::ascii_key('h'); - case 44: return KeyboardButton::ascii_key('j'); - case 45: return KeyboardButton::ascii_key('k'); - case 46: return KeyboardButton::ascii_key('l'); - case 47: return KeyboardButton::ascii_key(';'); - case 48: return KeyboardButton::ascii_key('\''); - case 49: return KeyboardButton::ascii_key('`'); - case 50: return KeyboardButton::lshift(); - case 51: return KeyboardButton::ascii_key('\\'); - case 52: return KeyboardButton::ascii_key('z'); - case 53: return KeyboardButton::ascii_key('x'); - case 54: return KeyboardButton::ascii_key('c'); - case 55: return KeyboardButton::ascii_key('v'); - case 56: return KeyboardButton::ascii_key('b'); - case 57: return KeyboardButton::ascii_key('n'); - case 58: return KeyboardButton::ascii_key('m'); - case 59: return KeyboardButton::ascii_key(','); - case 60: return KeyboardButton::ascii_key('.'); - case 61: return KeyboardButton::ascii_key('/'); - case 62: return KeyboardButton::rshift(); - case 63: return KeyboardButton::ascii_key('*'); - case 64: return KeyboardButton::lalt(); - case 65: return KeyboardButton::space(); - case 66: return KeyboardButton::caps_lock(); - case 67: return KeyboardButton::f1(); - case 68: return KeyboardButton::f2(); - case 69: return KeyboardButton::f3(); - case 70: return KeyboardButton::f4(); - case 71: return KeyboardButton::f5(); - case 72: return KeyboardButton::f6(); - case 73: return KeyboardButton::f7(); - case 74: return KeyboardButton::f8(); - case 75: return KeyboardButton::f9(); - case 76: return KeyboardButton::f10(); - case 77: return KeyboardButton::num_lock(); - case 78: return KeyboardButton::scroll_lock(); - case 79: return KeyboardButton::ascii_key('7'); - case 80: return KeyboardButton::ascii_key('8'); - case 81: return KeyboardButton::ascii_key('9'); - case 82: return KeyboardButton::ascii_key('-'); - case 83: return KeyboardButton::ascii_key('4'); - case 84: return KeyboardButton::ascii_key('5'); - case 85: return KeyboardButton::ascii_key('6'); - case 86: return KeyboardButton::ascii_key('+'); - case 87: return KeyboardButton::ascii_key('1'); - case 88: return KeyboardButton::ascii_key('2'); - case 89: return KeyboardButton::ascii_key('3'); - case 90: return KeyboardButton::ascii_key('0'); - case 91: return KeyboardButton::ascii_key('.'); - - case 95: return KeyboardButton::f11(); - case 96: return KeyboardButton::f12(); - - case 104: return KeyboardButton::enter(); - case 105: return KeyboardButton::rcontrol(); - case 106: return KeyboardButton::ascii_key('/'); - case 107: return KeyboardButton::print_screen(); - case 108: return KeyboardButton::ralt(); - - case 110: return KeyboardButton::home(); - case 111: return KeyboardButton::up(); - case 112: return KeyboardButton::page_up(); - case 113: return KeyboardButton::left(); - case 114: return KeyboardButton::right(); - case 115: return KeyboardButton::end(); - case 116: return KeyboardButton::down(); - case 117: return KeyboardButton::page_down(); - case 118: return KeyboardButton::insert(); - case 119: return KeyboardButton::del(); - - case 127: return KeyboardButton::pause(); - - case 133: return KeyboardButton::lmeta(); - case 134: return KeyboardButton::rmeta(); - case 135: return KeyboardButton::menu(); +#ifdef PHAVE_LINUX_INPUT_H + // Most X11 servers are configured to use the evdev driver, which + // adds 8 to the underlying evdev keycodes (not sure why). + // In any case, this means we can use the same mapping as our raw + // input code, which uses evdev directly. + int index = key - 8; + if (index >= 0) { + return EvdevInputDevice::map_button(index); } +#endif return ButtonHandle::none(); } diff --git a/panda/src/x11display/x11GraphicsWindow.h b/panda/src/x11display/x11GraphicsWindow.h index 5bcb8de0db..d279ecd443 100644 --- a/panda/src/x11display/x11GraphicsWindow.h +++ b/panda/src/x11display/x11GraphicsWindow.h @@ -74,7 +74,6 @@ protected: static Bool check_event(X11_Display *display, XEvent *event, char *arg); void open_raw_mice(); - void poll_raw_mice(); private: X11_Cursor get_cursor(const Filename &filename); @@ -89,6 +88,7 @@ protected: Colormap _colormap; XIC _ic; XVisualInfo *_visual_info; + GraphicsWindowInputDevice *_input; bool _have_xrandr; #ifdef HAVE_XRANDR From 58ddd77d5ed10f5ff87827c67fb5e2c06ad26a2f Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 11 Dec 2015 19:32:45 +0100 Subject: [PATCH 02/82] Support gamepads (and other devices) via evdev interface, query axis+button availability --- panda/src/device/evdevInputDevice.cxx | 50 ++++++++++++++++++++++++--- panda/src/device/inputDevice.cxx | 11 ++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index e03934d658..9b69ead27e 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -25,6 +25,11 @@ #define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7))) +static InputDevice::ControlAxis axis_map[] = { + InputDevice::C_left_x, InputDevice::C_left_y, InputDevice::C_left_trigger, + InputDevice::C_right_x, InputDevice::C_right_y, InputDevice::C_right_trigger +}; + TypeHandle EvdevInputDevice::_type_handle; //////////////////////////////////////////////////////////////////// @@ -95,12 +100,15 @@ do_poll() { //////////////////////////////////////////////////////////////////// bool EvdevInputDevice:: init_device() { - uint8_t evtypes[EV_MAX/8 + 1]; + nassertr(_fd >= 0, false); + + uint8_t evtypes[(EV_CNT + 7) >> 3]; + memset(evtypes, 0, sizeof(evtypes)); char name[256]; char uniq[256]; if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || ioctl(_fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0 || - ioctl(_fd, EVIOCGBIT(0, EV_MAX), &evtypes) < 0) { + ioctl(_fd, EVIOCGBIT(0, sizeof(evtypes)), evtypes) < 0) { close(_fd); _fd = -1; _is_connected = false; @@ -121,6 +129,35 @@ init_device() { _name = ((string)name) + "." + uniq; + if (test_bit(EV_ABS, evtypes)) { + // Check which axes are on the device. + uint8_t axes[(ABS_CNT + 7) >> 3]; + memset(axes, 0, sizeof(axes)); + + int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; + for (int i = 0; i < num_bits; ++i) { + if (test_bit(i, axes)) { + if (i >= 0 && i < 6) { + set_control_map(i, axis_map[i]); + } + } + } + } + + if (test_bit(EV_KEY, evtypes)) { + // Check which buttons are on the device. + uint8_t keys[(KEY_CNT + 7) >> 3]; + memset(keys, 0, sizeof(keys)); + + int num_bits = ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) << 3; + int bi = 0; + for (int i = 0; i < num_bits; ++i) { + if (test_bit(i, keys)) { + set_button_map(bi++, map_button(i)); + } + } + } + if (test_bit(EV_REL, evtypes)) { _flags |= IDF_has_pointer; } @@ -169,6 +206,7 @@ process_events() { int x = _pointer_data.get_x(); int y = _pointer_data.get_y(); + bool have_pointer = false; double time = ClockObject::get_global_clock()->get_frame_time(); ButtonHandle button; @@ -180,11 +218,11 @@ process_events() { case EV_REL: if (events[i].code == REL_X) x += events[i].value; if (events[i].code == REL_Y) y += events[i].value; + have_pointer = true; break; case EV_ABS: - if (events[i].code == ABS_X) x = events[i].value; - if (events[i].code == ABS_Y) y = events[i].value; + set_control_state(events[i].code, events[i].value / 32767.0); break; case EV_KEY: @@ -198,7 +236,9 @@ process_events() { } } - set_pointer(true, x, y, time); + if (have_pointer) { + set_pointer(true, x, y, time); + } return true; } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 25477ca913..a878a7027d 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -239,6 +239,17 @@ set_control_state(int index, double state) { _controls.resize(index + 1, AnalogState()); } + if (device_cat.is_spam()) { + device_cat.spam() + << "Changed control " << index; + + if (_controls[index]._axis != C_none) { + device_cat.spam(false) << " (" << _controls[index]._axis << ")"; + } + + device_cat.spam(false) << " to " << state << "\n"; + } + _controls[index]._state = state; _controls[index]._known = true; } From 35add447e64b674cb2a63323b928a20c7de6ef3d Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 12 Dec 2015 00:55:37 +0100 Subject: [PATCH 03/82] Build on Windows; add XInput gamepad implementation --- dtool/src/parser-inc/windows.h | 1 + panda/src/device/config_device.cxx | 5 + panda/src/device/inputDeviceManager.cxx | 9 +- panda/src/device/p3device_composite2.cxx | 2 +- panda/src/device/xInputDevice.cxx | 214 +++++++++++++++++++++ panda/src/device/xInputDevice.h | 50 +++++ panda/src/event/buttonEventList.h | 2 +- panda/src/windisplay/winGraphicsWindow.cxx | 75 ++++---- panda/src/windisplay/winGraphicsWindow.h | 6 +- 9 files changed, 321 insertions(+), 43 deletions(-) create mode 100644 panda/src/device/xInputDevice.cxx create mode 100644 panda/src/device/xInputDevice.h diff --git a/dtool/src/parser-inc/windows.h b/dtool/src/parser-inc/windows.h index bee8a5c25a..fe3c46e22e 100644 --- a/dtool/src/parser-inc/windows.h +++ b/dtool/src/parser-inc/windows.h @@ -39,6 +39,7 @@ typedef unsigned long ULONG_PTR; // http://msdn.microsoft.com/en-us/library/cc230309.aspx typedef bool BOOL; typedef unsigned long DWORD; +typedef unsigned short WORD; typedef long LONG; typedef long UINT; typedef unsigned long ULONG; diff --git a/panda/src/device/config_device.cxx b/panda/src/device/config_device.cxx index 84e290be91..c3e7613e0d 100644 --- a/panda/src/device/config_device.cxx +++ b/panda/src/device/config_device.cxx @@ -28,6 +28,7 @@ #include "linuxJoystickDevice.h" #include "trackerNode.h" #include "virtualMouse.h" +#include "xInputDevice.h" #include "dconfig.h" @@ -74,4 +75,8 @@ init_libdevice() { EvdevInputDevice::init_type(); LinuxJoystickDevice::init_type(); #endif + +#ifdef _WIN32 + XInputDevice::init_type(); +#endif } diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index d67e21208f..aa799231e3 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -30,9 +30,10 @@ InputDeviceManager *InputDeviceManager::_global_ptr = NULL; // Access: Private // Description: //////////////////////////////////////////////////////////////////// +#ifdef PHAVE_LINUX_INPUT_H InputDeviceManager:: InputDeviceManager() : _inotify_fd(-1) { -#ifdef PHAVE_LINUX_INPUT_H + // Scan /dev/input for a list of input devices. DIR *dir = opendir("/dev/input"); if (dir) { @@ -70,8 +71,12 @@ InputDeviceManager() : _inotify_fd(-1) { device_cat.error() << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; } -#endif } +#else +InputDeviceManager:: +InputDeviceManager() { +} +#endif //////////////////////////////////////////////////////////////////// // Function: InputDeviceManager::Destructor diff --git a/panda/src/device/p3device_composite2.cxx b/panda/src/device/p3device_composite2.cxx index 294e5d69ae..674c1865fe 100644 --- a/panda/src/device/p3device_composite2.cxx +++ b/panda/src/device/p3device_composite2.cxx @@ -6,4 +6,4 @@ #include "trackerData.cxx" #include "trackerNode.cxx" #include "virtualMouse.cxx" - +#include "xInputDevice.cxx" diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx new file mode 100644 index 0000000000..92ab2928c0 --- /dev/null +++ b/panda/src/device/xInputDevice.cxx @@ -0,0 +1,214 @@ +// Filename: xInputDevice.cxx +// Created by: rdb (21Jul15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "xInputDevice.h" + +#ifdef _WIN32 +#include "gamepadButton.h" + +#include + +#ifndef XUSER_MAX_COUNT +#define XUSER_MAX_COUNT 4 +#endif + +#ifndef XINPUT_CAPS_FFB_SUPPORTED +#define XINPUT_CAPS_FFB_SUPPORTED 0x0001 +#endif + +typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); +typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); +//typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); + +static pXInputGetState get_state = NULL; +static pXInputGetCapabilities get_capabilities = NULL; +//static pXInputGetBatteryInformation get_battery_information = NULL; + +bool XInputDevice::_initialized = false; + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::Constructor +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +XInputDevice:: +XInputDevice(DWORD user_index) : + _index(user_index), + _last_packet(-1), + _last_buttons(0) { + + nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT); + + if (!_initialized) { + nassertv(init_xinput()); + } + + if (get_capabilities != NULL) { + XINPUT_CAPABILITIES caps; + if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS) { + _is_connected = true; + } else { + _is_connected = false; + } + + if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { + _device_class = DC_gamepad; + } else { + _device_class = DC_unknown; + } + + if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { + _flags |= IDF_has_vibration; + } + } + +#if 0 + if (get_battery_information != NULL) { + XINPUT_BATTERY_INFORMATION batt; + if (XInputGetBatteryInformation(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) { + if (batt.BatteryType == BATTERY_TYPE_DISCONNECTED) { + _is_connected = false; + + } else if (batt.BatteryType != BATTERY_TYPE_WIRED) { + // This device has a battery. Report the battery level. + _is_connected = true; + _flags |= IDF_has_battery; + _battery_level = batt.BatteryLevel; + _max_battery_level = BATTERY_LEVEL_FULL; + } + } + } +#endif + + _buttons.resize(16); + + // Get the initial state. + XINPUT_STATE state; + if (get_state(_index, &state) != ERROR_SUCCESS) { + // Device was disconnected. + _is_connected = false; + return; + } + + WORD buttons = state.Gamepad.wButtons; + + set_button_map(0, GamepadButton::dpad_up()); + set_button_state(0, (buttons & XINPUT_GAMEPAD_DPAD_UP) != 0); + set_button_map(1, GamepadButton::dpad_down()); + set_button_state(1, (buttons & XINPUT_GAMEPAD_DPAD_DOWN) != 0); + set_button_map(2, GamepadButton::dpad_left()); + set_button_state(2, (buttons & XINPUT_GAMEPAD_DPAD_LEFT) != 0); + set_button_map(3, GamepadButton::dpad_right()); + set_button_state(3, (buttons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0); + set_button_map(4, GamepadButton::start()); + set_button_state(4, (buttons & XINPUT_GAMEPAD_START) != 0); + set_button_map(5, GamepadButton::back()); + set_button_state(5, (buttons & XINPUT_GAMEPAD_BACK) != 0); + set_button_map(6, GamepadButton::lstick()); + set_button_state(6, (buttons & XINPUT_GAMEPAD_LEFT_THUMB) != 0); + set_button_map(7, GamepadButton::rstick()); + set_button_state(7, (buttons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0); + set_button_map(8, GamepadButton::lshoulder()); + set_button_state(8, (buttons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0); + set_button_map(9, GamepadButton::rshoulder()); + set_button_state(9, (buttons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0); + set_button_map(10, GamepadButton::guide()); + set_button_state(10, (buttons & 0x0400) != 0); + set_button_map(11, GamepadButton::action_a()); + set_button_state(11, (buttons & XINPUT_GAMEPAD_A) != 0); + set_button_map(12, GamepadButton::action_b()); + set_button_state(12, (buttons & XINPUT_GAMEPAD_B) != 0); + set_button_map(13, GamepadButton::action_x()); + set_button_state(13, (buttons & XINPUT_GAMEPAD_X) != 0); + set_button_map(14, GamepadButton::action_y()); + set_button_state(14, (buttons & XINPUT_GAMEPAD_Y) != 0); + + _last_buttons = buttons; +} + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::init +// Access: Protected, Static +// Description: Initializes the XInput library. +//////////////////////////////////////////////////////////////////// +bool XInputDevice:: +init_xinput() { + _initialized = true; + HMODULE module = LoadLibraryA("Xinput1_4.dll"); + if (module) { + // Undocumented version (XInputGetStateEx) that includes a + // state bit for the guide button. + get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100)); + if (get_state == NULL) { + get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState"); + if (get_state == NULL) { + device_cat.error() + << "Failed to find function XInputGetState in XInput DLL.\n"; + return false; + } + } + + get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); + return true; + } + + device_cat.error() + << "Failed to load Xinput1_4.dll.\n"; + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::do_poll +// Access: Public, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void XInputDevice:: +do_poll() { + XINPUT_STATE state; + + if (get_state(_index, &state) != ERROR_SUCCESS) { + // Device was disconnected. + _is_connected = false; + return; + } else { + _is_connected = true; + } + + if (state.dwPacketNumber == _last_packet) { + // No change since last time we asked. + return; + } + + WORD changed_buttons = _last_buttons ^ state.Gamepad.wButtons; + + WORD mask = 1; + for (int i = 0; i < 16; ++i) { + if (changed_buttons & mask) { + set_button_state(i, (state.Gamepad.wButtons & mask) != 0); + } + mask <<= 1; + if (i == 10) { + mask <<= 1; + } + } + + _last_buttons = state.Gamepad.wButtons; + _last_packet = state.dwPacketNumber; +} + +#endif // _WIN32 diff --git a/panda/src/device/xInputDevice.h b/panda/src/device/xInputDevice.h new file mode 100644 index 0000000000..7abc0eafc5 --- /dev/null +++ b/panda/src/device/xInputDevice.h @@ -0,0 +1,50 @@ +// Filename: xInputDevice.h +// Created by: rdb (21Jul15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef XINPUTDEVICE_H +#define XINPUTDEVICE_H + +#include "pandabase.h" +#include "inputDevice.h" + +#ifdef _WIN32 + +//////////////////////////////////////////////////////////////////// +// Class : XInputDevice +// Description : This uses Microsoft's XInput library to interface +// with an Xbox 360 game controller. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE XInputDevice : public InputDevice { +PUBLISHED: + XInputDevice(DWORD user_index); + + static bool init_xinput(); + +public: + virtual void do_poll(); + +private: + DWORD _index; + DWORD _last_packet; + WORD _last_buttons; + + static bool _initialized; + + // There are only four of these in existence. + //static XInputDevice _devices[4]; +}; + +#endif // _WIN32 + +#endif diff --git a/panda/src/event/buttonEventList.h b/panda/src/event/buttonEventList.h index ead1a5968c..ca4ae298dc 100644 --- a/panda/src/event/buttonEventList.h +++ b/panda/src/event/buttonEventList.h @@ -35,7 +35,7 @@ class DatagramIterator; // is desired. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_EVENT ButtonEventList : public ParamValueBase { -public: +PUBLISHED: INLINE ButtonEventList(); INLINE ButtonEventList(const ButtonEventList ©); INLINE void operator = (const ButtonEventList ©); diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 4e04206426..9cd90ad3ee 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -146,7 +146,7 @@ move_pointer(int device, int x, int y) { if (device == 0) { // Move the system mouse pointer. if (!_properties.get_foreground() ) - // !_input_devices[0].get_pointer().get_in_window()) + // !_input->get_pointer().get_in_window()) { // If the window doesn't have input focus, or the mouse isn't // currently within the window, forget it. @@ -157,14 +157,14 @@ move_pointer(int device, int x, int y) { get_client_rect_screen(_hWnd, &view_rect); SetCursorPos(view_rect.left + x, view_rect.top + y); - _input_devices[0].set_pointer_in_window(x, y); + _input->set_pointer_in_window(x, y); return true; } else { // Move a raw mouse. if ((device < 1)||(device >= (int)_input_devices.size())) { return false; } - _input_devices[device].set_pointer_in_window(x, y); + //_input_devices[device].set_pointer_in_window(x, y); return true; } } @@ -587,7 +587,6 @@ open_window() { // initializes a parallel array, _input_device_handle, // with the win32 handle of each raw input device. //////////////////////////////////////////////////////////////////// - void WinGraphicsWindow:: initialize_input_devices() { UINT nInputDevices; @@ -597,9 +596,10 @@ initialize_input_devices() { // Clear the handle array, and set up the system keyboard/mouse memset(_input_device_handle, 0, sizeof(_input_device_handle)); - GraphicsWindowInputDevice device = + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); add_input_device(device); + _input = device; // Try initializing the Raw Input function pointers. if (pRegisterRawInputDevices==0) { @@ -656,9 +656,9 @@ initialize_input_devices() { } if (pound2) *pound2 = '.'; _input_device_handle[_input_devices.size()] = pRawInputDeviceList[i].hDevice; - GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_only(this, psName); - device.set_pointer_in_window(0,0); - add_input_device(device); + //GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_only(this, psName); + //device.set_pointer_in_window(0,0); + //add_input_device(device); } } } @@ -1578,8 +1578,8 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } SetCapture(hwnd); - _input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); - _input_devices[0].button_down(MouseButton::button(0), get_message_time()); + _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); + _input->button_down(MouseButton::button(0), get_message_time()); // A button-click in the window means to grab the keyboard focus. set_focus(); @@ -1590,8 +1590,8 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } SetCapture(hwnd); - _input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); - _input_devices[0].button_down(MouseButton::button(1), get_message_time()); + _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); + _input->button_down(MouseButton::button(1), get_message_time()); // A button-click in the window means to grab the keyboard focus. set_focus(); return 0; @@ -1601,8 +1601,8 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } SetCapture(hwnd); - _input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); - _input_devices[0].button_down(MouseButton::button(2), get_message_time()); + _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); + _input->button_down(MouseButton::button(2), get_message_time()); // A button-click in the window means to grab the keyboard focus. set_focus(); return 0; @@ -1614,11 +1614,11 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { } SetCapture(hwnd); int whichButton = GET_XBUTTON_WPARAM(wparam); - _input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); + _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); if (whichButton == XBUTTON1) { - _input_devices[0].button_down(MouseButton::button(3), get_message_time()); + _input->button_down(MouseButton::button(3), get_message_time()); } else if (whichButton == XBUTTON2) { - _input_devices[0].button_down(MouseButton::button(4), get_message_time()); + _input->button_down(MouseButton::button(4), get_message_time()); } } return 0; @@ -1628,7 +1628,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } ReleaseCapture(); - _input_devices[0].button_up(MouseButton::button(0), get_message_time()); + _input->button_up(MouseButton::button(0), get_message_time()); return 0; case WM_MBUTTONUP: @@ -1636,7 +1636,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } ReleaseCapture(); - _input_devices[0].button_up(MouseButton::button(1), get_message_time()); + _input->button_up(MouseButton::button(1), get_message_time()); return 0; case WM_RBUTTONUP: @@ -1644,7 +1644,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } ReleaseCapture(); - _input_devices[0].button_up(MouseButton::button(2), get_message_time()); + _input->button_up(MouseButton::button(2), get_message_time()); return 0; case WM_XBUTTONUP: @@ -1655,9 +1655,9 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { ReleaseCapture(); int whichButton = GET_XBUTTON_WPARAM(wparam); if (whichButton == XBUTTON1) { - _input_devices[0].button_up(MouseButton::button(3), get_message_time()); + _input->button_up(MouseButton::button(3), get_message_time()); } else if (whichButton == XBUTTON2) { - _input_devices[0].button_up(MouseButton::button(4), get_message_time()); + _input->button_up(MouseButton::button(4), get_message_time()); } } return 0; @@ -1777,7 +1777,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (ime_aware) { wstring ws; - _input_devices[0].candidate(ws, 0, 0, 0); + _input->candidate(ws, 0, 0, 0); } break; @@ -1808,7 +1808,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { ime_buffer, ime_buffer_size_bytes); size_t num_chars = result_size / sizeof(wchar_t); for (size_t i = 0; i < num_chars; ++i) { - _input_devices[0].keystroke(ime_buffer[i]); + _input->keystroke(ime_buffer[i]); } } @@ -1821,7 +1821,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { result_size = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, ime_buffer, ime_buffer_size); size_t num_chars = result_size / sizeof(wchar_t); - _input_devices[0].candidate(wstring(ime_buffer, num_chars), + _input->candidate(wstring(ime_buffer, num_chars), min(cursor_pos, delta_start), max(cursor_pos, delta_start), cursor_pos); @@ -1843,7 +1843,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // poorly defined. Now we are using RegisterClassW etc., which // means WM_CHAR is absolutely supposed to be utf-16. if (!_ime_open) { - _input_devices[0].keystroke(wparam); + _input->keystroke(wparam); } break; @@ -1975,7 +1975,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (lptstr != NULL) { char *pChar; for (pChar=lptstr; *pChar!=NULL; pChar++) { - _input_devices[0].keystroke((uchar)*pChar); + _input->keystroke((uchar)*pChar); } GlobalUnlock(hglb); } @@ -2098,7 +2098,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { << "killfocus\n"; } - _input_devices[0].focus_lost(get_message_time()); + _input->focus_lost(get_message_time()); properties.set_foreground(false); system_changed_properties(properties); break; @@ -2409,9 +2409,9 @@ show_error_message(DWORD message_id) { //////////////////////////////////////////////////////////////////// void WinGraphicsWindow:: handle_keypress(ButtonHandle key, int x, int y, double time) { - _input_devices[0].set_pointer_in_window(x, y); + _input->set_pointer_in_window(x, y); if (key != ButtonHandle::none()) { - _input_devices[0].button_down(key, time); + _input->button_down(key, time); } } @@ -2425,7 +2425,7 @@ handle_keypress(ButtonHandle key, int x, int y, double time) { void WinGraphicsWindow:: handle_keyresume(ButtonHandle key, double time) { if (key != ButtonHandle::none()) { - _input_devices[0].button_resume_down(key, time); + _input->button_resume_down(key, time); } } @@ -2437,7 +2437,7 @@ handle_keyresume(ButtonHandle key, double time) { void WinGraphicsWindow:: handle_keyrelease(ButtonHandle key, double time) { if (key != ButtonHandle::none()) { - _input_devices[0].button_up(key, time); + _input->button_up(key, time); } } @@ -2449,7 +2449,7 @@ handle_keyrelease(ButtonHandle key, double time) { void WinGraphicsWindow:: handle_raw_keypress(ButtonHandle key, double time) { if (key != ButtonHandle::none()) { - _input_devices[0].raw_button_down(key, time); + _input->raw_button_down(key, time); } } @@ -2461,7 +2461,7 @@ handle_raw_keypress(ButtonHandle key, double time) { void WinGraphicsWindow:: handle_raw_keyrelease(ButtonHandle key, double time) { if (key != ButtonHandle::none()) { - _input_devices[0].raw_button_up(key, time); + _input->raw_button_up(key, time); } } @@ -2777,7 +2777,7 @@ handle_raw_input(HRAWINPUT hraw) { if (raw->header.hDevice == 0) { return; } - +/* for (int i = 1; i < (int)(_input_devices.size()); ++i) { if (_input_device_handle[i] == raw->header.hDevice) { int adjx = raw->data.mouse.lLastX; @@ -2823,6 +2823,7 @@ handle_raw_input(HRAWINPUT hraw) { } } } +*/ } //////////////////////////////////////////////////////////////////// @@ -2832,7 +2833,7 @@ handle_raw_input(HRAWINPUT hraw) { //////////////////////////////////////////////////////////////////// bool WinGraphicsWindow:: handle_mouse_motion(int x, int y) { - _input_devices[0].set_pointer_in_window(x, y); + _input->set_pointer_in_window(x, y); return false; } @@ -2844,7 +2845,7 @@ handle_mouse_motion(int x, int y) { void WinGraphicsWindow:: handle_mouse_exit() { // note: 'mouse_motion' is considered the 'entry' event - _input_devices[0].set_pointer_out_of_window(); + _input->set_pointer_out_of_window(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/windisplay/winGraphicsWindow.h b/panda/src/windisplay/winGraphicsWindow.h index 1b3ee93f95..91350ee3af 100644 --- a/panda/src/windisplay/winGraphicsWindow.h +++ b/panda/src/windisplay/winGraphicsWindow.h @@ -110,7 +110,7 @@ protected: virtual DWORD make_style(bool fullscreen); - virtual void reconsider_fullscreen_size(DWORD &x_size, DWORD &y_size, + virtual void reconsider_fullscreen_size(DWORD &x_size, DWORD &y_size, DWORD &bitdepth); virtual void support_overlay_window(bool flag); @@ -179,6 +179,8 @@ private: bool _lalt_down; bool _ralt_down; + GraphicsWindowInputDevice *_input; + // following adds support platform specfic window processing // functions. typedef pset WinProcClasses; @@ -276,7 +278,7 @@ private: return get_class_type(); } virtual TypeHandle force_init_type() {init_type(); return get_class_type();} - + private: static TypeHandle _type_handle; }; From dd8344455cc426faa9e44206d8bfd20134b3c023 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 12 Dec 2015 22:07:43 +0100 Subject: [PATCH 04/82] XInput gamepad control states, battery information, HID properties --- dtool/src/parser-inc/windows.h | 1 + makepanda/makepanda.py | 5 +- panda/src/device/inputDevice.I | 2 + panda/src/device/inputDevice.cxx | 18 ++-- panda/src/device/inputDevice.h | 4 + panda/src/device/inputDeviceManager.cxx | 6 +- panda/src/device/xInputDevice.cxx | 115 ++++++++++++++++++++++-- 7 files changed, 127 insertions(+), 24 deletions(-) diff --git a/dtool/src/parser-inc/windows.h b/dtool/src/parser-inc/windows.h index fe3c46e22e..4a36e5b2d2 100644 --- a/dtool/src/parser-inc/windows.h +++ b/dtool/src/parser-inc/windows.h @@ -42,6 +42,7 @@ typedef unsigned long DWORD; typedef unsigned short WORD; typedef long LONG; typedef long UINT; +typedef unsigned char BYTE; typedef unsigned long ULONG; typedef long long LONGLONG; typedef long HRESULT; diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index d11d0825bd..07d8950606 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -578,6 +578,7 @@ if (COMPILER == "MSVC"): LibName("WINGDI", "gdi32.lib") LibName("ADVAPI", "advapi32.lib") LibName("IPHLPAPI", "iphlpapi.lib") + LibName("SETUPAPI", "setupapi.lib") LibName("GL", "opengl32.lib") LibName("GLES", "libgles_cm.lib") LibName("GLES2", "libGLESv2.lib") @@ -3849,8 +3850,8 @@ if (not RUNTIME): # if (not RUNTIME): - OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', - 'TIFF', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', + OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'TIFF', + 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', 'SETUPAPI', 'SQUISH', 'NVIDIACG', 'VORBIS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI'] TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx') diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 99962b7750..8f04226896 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -21,6 +21,8 @@ INLINE InputDevice:: InputDevice() : _flags(0), _device_class(DC_unknown), + _vendor_id(0), + _product_id(0), _is_connected(false), _event_sequence(0), _enable_pointer_events(false), diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index a878a7027d..4637673672 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -32,6 +32,8 @@ InputDevice(const string &name, DeviceClass dev_class, int flags) : _name(name), _flags(flags), _device_class(dev_class), + _vendor_id(0), + _product_id(0), _is_connected(true), _event_sequence(0), _enable_pointer_events(false), @@ -48,7 +50,7 @@ InputDevice(const string &name, DeviceClass dev_class, int flags) : //////////////////////////////////////////////////////////////////// InputDevice:: InputDevice(const InputDevice ©) { - *this = copy; + nassertv(false); } //////////////////////////////////////////////////////////////////// @@ -58,16 +60,7 @@ InputDevice(const InputDevice ©) { //////////////////////////////////////////////////////////////////// void InputDevice:: operator = (const InputDevice ©) { - LightMutexHolder holder(_lock); - LightMutexHolder holder1(copy._lock); - _name = copy._name; - _flags = copy._flags; - _is_connected = copy._is_connected; - _event_sequence = copy._event_sequence; - _enable_pointer_events = copy._enable_pointer_events; - _pointer_data = copy._pointer_data; - _button_events = copy._button_events; - _pointer_events = copy._pointer_events; + nassertv(false); } //////////////////////////////////////////////////////////////////// @@ -315,10 +308,9 @@ output(ostream &out) const { if (_battery_level > 0 && _max_battery_level > 0) { out << " ["; short i = 0; - for (; i < _battery_level - 1; ++i) { + for (; i < _battery_level; ++i) { out << '='; } - out << '/'; for (; i < _max_battery_level; ++i) { out << ' '; } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 407e6cdb77..758ea366f8 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -206,9 +206,13 @@ protected: LightMutex _lock; string _name; + string _serial_number; + string _manufacturer; DeviceClass _device_class; int _flags; int _event_sequence; + short _vendor_id; + short _product_id; bool _is_connected; bool _enable_pointer_events; PointerData _pointer_data; diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index aa799231e3..90f1e1921f 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -143,7 +143,7 @@ add_device(InputDevice *device) { LightMutexHolder holder(_lock); _all_devices.push_back(device); } - throw_event("device-added", device); + throw_event("connect-device", device); } //////////////////////////////////////////////////////////////////// @@ -163,7 +163,7 @@ remove_device(InputDevice *device) { _all_devices.erase(it); } - throw_event("device-removed", device); + throw_event("disconnect-device", device); } //////////////////////////////////////////////////////////////////// @@ -220,7 +220,7 @@ poll() { device_cat.info() << "Removed input device " << *device << "\n"; } - throw_event("device-removed", device.p()); + throw_event("disconnect-device", device.p()); } } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 92ab2928c0..13a2ffdc19 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -18,6 +18,7 @@ #include "gamepadButton.h" #include +#include #ifndef XUSER_MAX_COUNT #define XUSER_MAX_COUNT 4 @@ -27,13 +28,47 @@ #define XINPUT_CAPS_FFB_SUPPORTED 0x0001 #endif +#ifndef BATTERY_DEVTYPE_GAMEPAD +#define BATTERY_DEVTYPE_GAMEPAD 0x00 +#endif + +#ifndef BATTERY_TYPE_DISCONNECTED +#define BATTERY_TYPE_DISCONNECTED 0x00 +#endif + +#ifndef BATTERY_TYPE_WIRED +#define BATTERY_TYPE_WIRED 0x01 +#endif + +#ifndef BATTERY_LEVEL_FULL +#define BATTERY_LEVEL_FULL 0x03 +#endif + +typedef struct _XINPUT_BATTERY_INFORMATION { + BYTE BatteryType; + BYTE BatteryLevel; +} XINPUT_BATTERY_INFORMATION; + +// Undocumented, I figured out how this looks by trial and error. +struct XINPUT_BUSINFO { + WORD VendorID; + WORD ProductID; + WORD RevisionID; + WORD Unknown1; // Unknown - padding? + DWORD InstanceID; + DWORD Unknown2; + //WORD Unknown3; +}; + typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); -//typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); +typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); +typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *); static pXInputGetState get_state = NULL; static pXInputGetCapabilities get_capabilities = NULL; -//static pXInputGetBatteryInformation get_battery_information = NULL; +static pXInputGetBatteryInformation get_battery_information = NULL; +static pXInputGetBaseBusInformation get_base_bus_information = NULL; bool XInputDevice::_initialized = false; @@ -73,10 +108,9 @@ XInputDevice(DWORD user_index) : } } -#if 0 if (get_battery_information != NULL) { XINPUT_BATTERY_INFORMATION batt; - if (XInputGetBatteryInformation(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) { + if (get_battery_information(_index, 0, &batt) == ERROR_SUCCESS) { if (batt.BatteryType == BATTERY_TYPE_DISCONNECTED) { _is_connected = false; @@ -89,8 +123,52 @@ XInputDevice(DWORD user_index) : } } } -#endif + // Get information about the USB device. + // This is not documented at all. I'm probably the first to try this. + XINPUT_BUSINFO businfo; + if (get_base_bus_information != NULL && + get_base_bus_information(0, &businfo) == ERROR_SUCCESS) { + _vendor_id = businfo.VendorID; + _product_id = businfo.ProductID; + + { + // Reformat the serial number into its original hex string form. + char sn[10]; + sprintf(sn, "%08X", businfo.InstanceID); + _serial_number.assign(sn, 8); + } + + // Get information about the device from Windows. For that, we'll + // first need to construct the device path. Fortunately, we now have + // enough information to do so. + char path[32]; + sprintf(path, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID); + + DEVINST inst; + if (CM_Locate_DevNodeA(&inst, path, 0) != 0) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Could not locate device node " << path << "\n"; + } + } else { + // Get the device properties we need. + char buffer[4096]; + ULONG buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) { + _name.assign(buffer); + } + buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) { + _manufacturer.assign(buffer); + } + } + } else { + // We need something to name it. + _name = "XInput Device"; + } + + _controls.resize(6); _buttons.resize(16); // Get the initial state. @@ -103,6 +181,19 @@ XInputDevice(DWORD user_index) : WORD buttons = state.Gamepad.wButtons; + set_control_map(0, C_left_trigger); + set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); + set_control_map(1, C_right_trigger); + set_control_state(1, state.Gamepad.bRightTrigger / 255.0); + set_control_map(2, C_left_x); + set_control_state(2, state.Gamepad.sThumbLX / 32767.0); + set_control_map(3, C_left_y); + set_control_state(3, state.Gamepad.sThumbLY / 32767.0); + set_control_map(4, C_right_x); + set_control_state(4, state.Gamepad.sThumbRX / 32767.0); + set_control_map(5, C_right_y); + set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + set_button_map(0, GamepadButton::dpad_up()); set_button_state(0, (buttons & XINPUT_GAMEPAD_DPAD_UP) != 0); set_button_map(1, GamepadButton::dpad_down()); @@ -146,6 +237,9 @@ bool XInputDevice:: init_xinput() { _initialized = true; HMODULE module = LoadLibraryA("Xinput1_4.dll"); + if (!module) { + module = LoadLibraryA("Xinput1_3.dll"); + } if (module) { // Undocumented version (XInputGetStateEx) that includes a // state bit for the guide button. @@ -160,11 +254,13 @@ init_xinput() { } get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); + get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation"); + get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104)); return true; } device_cat.error() - << "Failed to load Xinput1_4.dll.\n"; + << "Failed to load XInput DLL.\n"; return false; } @@ -207,6 +303,13 @@ do_poll() { } } + set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); + set_control_state(1, state.Gamepad.bRightTrigger / 255.0); + set_control_state(2, state.Gamepad.sThumbLX / 32767.0); + set_control_state(3, state.Gamepad.sThumbLY / 32767.0); + set_control_state(4, state.Gamepad.sThumbRX / 32767.0); + set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + _last_buttons = state.Gamepad.wButtons; _last_packet = state.dwPacketNumber; } From c256faccd6de49571bb92e5ccaaab27f5b9aae27 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 Dec 2015 11:34:33 +0100 Subject: [PATCH 05/82] Forgot to add parser-inc file for XInput --- dtool/src/parser-inc/XInput.h | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 dtool/src/parser-inc/XInput.h diff --git a/dtool/src/parser-inc/XInput.h b/dtool/src/parser-inc/XInput.h new file mode 100644 index 0000000000..f770cf3d71 --- /dev/null +++ b/dtool/src/parser-inc/XInput.h @@ -0,0 +1,2 @@ +struct XINPUT_STATE; +struct XINPUT_CAPABILITIES; From d20d6169183df18e941300cebf836008935486ad Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 16 Dec 2015 18:30:26 +0100 Subject: [PATCH 06/82] XInput gamepad hotplugging, add base.devices.gamepads --- direct/src/showbase/ShowBase.py | 7 + panda/src/device/inputDevice.h | 1 + panda/src/device/inputDeviceManager.cxx | 103 ++++++-- panda/src/device/inputDeviceManager.h | 26 +- panda/src/device/inputDeviceSet.I | 44 ++++ panda/src/device/inputDeviceSet.cxx | 170 +++++++++++++ panda/src/device/inputDeviceSet.h | 67 ++++++ panda/src/device/p3device_composite1.cxx | 1 + panda/src/device/xInputDevice.cxx | 289 ++++++++++++++--------- panda/src/device/xInputDevice.h | 18 +- 10 files changed, 592 insertions(+), 134 deletions(-) create mode 100755 panda/src/device/inputDeviceSet.I create mode 100755 panda/src/device/inputDeviceSet.cxx create mode 100755 panda/src/device/inputDeviceSet.h diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 89168d57d6..621fb75b6b 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -295,6 +295,10 @@ class ShowBase(DirectObject.DirectObject): self.physicsMgrEnabled = 0 self.physicsMgrAngular = 0 + ## This is the global input device manager, which keeps track of + ## connected input devices. + self.devices = InputDeviceManager.getGlobalPtr() + self.createStats() self.AppHasAudioFocus = 1 @@ -1823,6 +1827,9 @@ class ShowBase(DirectObject.DirectObject): return Task.cont def __dataLoop(self, state): + # Check if there were newly connected devices. + self.devices.update() + # traverse the data graph. This reads all the control # inputs (from the mouse and keyboard, for instance) and also # directly acts upon them (for instance, to move the avatar). diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 758ea366f8..a6aaa5a015 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -62,6 +62,7 @@ PUBLISHED: // This enum contains information that can be used to identify the // type of input device. enum DeviceClass { + // It is not known what type of device this is. DC_unknown, // This means that the device doesn't correspond to a physical diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 90f1e1921f..44b038adda 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -23,6 +23,12 @@ #include #endif +static ConfigVariableDouble xinput_detection_delay +("xinput-detection-delay", 0.5, + PRC_DESC("How many seconds to wait between each check to see whether " + "an XInput has been connected. This check is not done every " + "frame in order to prevent slowdown.")); + InputDeviceManager *InputDeviceManager::_global_ptr = NULL; //////////////////////////////////////////////////////////////////// @@ -72,6 +78,34 @@ InputDeviceManager() : _inotify_fd(-1) { << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; } } +#elif defined(_WIN32) +InputDeviceManager:: +InputDeviceManager() : + _xinput_device0(0), + _xinput_device1(1), + _xinput_device2(2), + _xinput_device3(3), + _last_detection(0.0) { + + // These devices are bound to the lifetime of the input manager. + _xinput_device0.local_object(); + _xinput_device1.local_object(); + _xinput_device2.local_object(); + _xinput_device3.local_object(); + + if (_xinput_device0.is_connected()) { + _connected_devices.add_device(&_xinput_device0); + } + if (_xinput_device1.is_connected()) { + _connected_devices.add_device(&_xinput_device1); + } + if (_xinput_device2.is_connected()) { + _connected_devices.add_device(&_xinput_device2); + } + if (_xinput_device3.is_connected()) { + _connected_devices.add_device(&_xinput_device3); + } +} #else InputDeviceManager:: InputDeviceManager() { @@ -124,7 +158,7 @@ consider_add_linux_device(const string &name) { } _devices_by_path[name] = device; - _all_devices.push_back(MOVE(device)); + _connected_devices.add_device(MOVE(device)); return true; } @@ -133,46 +167,67 @@ consider_add_linux_device(const string &name) { #endif //////////////////////////////////////////////////////////////////// -// Function: InputDeviceManager::add_device +// Function: InputDeviceManager::get_gamepads // Access: Public -// Description: Called when a new device has been discovered. +// Description: Returns all currently connected gamepad devices. +//////////////////////////////////////////////////////////////////// +InputDeviceSet InputDeviceManager:: +get_gamepads() const { + InputDeviceSet gamepads; + LightMutexHolder holder(_lock); + + for (size_t i = 0; i < _connected_devices.size(); ++i) { + InputDevice *device = _connected_devices[i]; + if (device->get_device_class() == InputDevice::DC_gamepad) { + gamepads.add_device(device); + } + } + + return gamepads; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::add_device +// Access: Published +// Description: Called when a new device has been discovered. This +// may also be used to register virtual devices. +// +// This sends a connect-device event. //////////////////////////////////////////////////////////////////// void InputDeviceManager:: add_device(InputDevice *device) { { LightMutexHolder holder(_lock); - _all_devices.push_back(device); + _connected_devices.add_device(device); } throw_event("connect-device", device); } //////////////////////////////////////////////////////////////////// // Function: InputDeviceManager::remove_device -// Access: Public -// Description: Called when a device has been removed. +// Access: Published +// Description: Called when a device has been removed, or when a +// device should otherwise no longer be tracked. +// +// This sends a disconnect-device event. //////////////////////////////////////////////////////////////////// void InputDeviceManager:: remove_device(InputDevice *device) { { LightMutexHolder holder(_lock); - - InputDevices::iterator it; - it = std::find(_all_devices.begin(), _all_devices.end(), device); - nassertv_always(it != _all_devices.end()); - - _all_devices.erase(it); + _connected_devices.remove_device(device); } throw_event("disconnect-device", device); } //////////////////////////////////////////////////////////////////// -// Function: InputDeviceManager::poll +// Function: InputDeviceManager::update // Access: Public // Description: Polls the system to see if there are any new devices. //////////////////////////////////////////////////////////////////// void InputDeviceManager:: -poll() { +update() { #ifdef PHAVE_LINUX_INPUT_H // We use inotify to tell us whether a device was added, removed, // or has changed permissions to allow us to access it. @@ -213,8 +268,8 @@ poll() { InputDevices::iterator it2; _devices_by_path.erase(it); - it2 = std::find(_all_devices.begin(), _all_devices.end(), device); - _all_devices.erase(it2); + it2 = std::find(_connected_devices.begin(), _connected_devices.end(), device); + _connected_devices.erase(it2); if (device_cat.is_info()) { device_cat.info() @@ -242,4 +297,20 @@ poll() { ptr += sizeof(inotify_event) + event->len; } #endif + +#ifdef _WIN32 + // XInput doesn't provide a very good hot-plugging interface. We just + // check if it's connected every so often. Perhaps we can switch to + // using RegisterDeviceNotification in the future. + double time_now = ClockObject::get_global_clock()->get_real_time(); + if (time_now - _last_detection > xinput_detection_delay.get_value()) { + // I've heard this can be quite slow if no device is detected. We + // should probably move it to a thread. + _xinput_device0.detect(this); + _xinput_device1.detect(this); + _xinput_device2.detect(this); + _xinput_device3.detect(this); + _last_detection = time_now; + } +#endif } diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 3c05cf96fd..3f2c2cea43 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -18,6 +18,11 @@ #include "pandabase.h" #include "lightMutex.h" #include "inputDevice.h" +#include "inputDeviceSet.h" + +#ifdef _WIN32 +#include "xinputDevice.h" +#endif //////////////////////////////////////////////////////////////////// // Class : InputDeviceManager @@ -35,14 +40,19 @@ private: #endif public: + InputDeviceSet get_gamepads() const; + +PUBLISHED: void add_device(InputDevice *device); void remove_device(InputDevice *device); -PUBLISHED: - void poll(); + void update(); INLINE static InputDeviceManager *get_global_ptr(); + // The set of all currently connected gamepad devices. + MAKE_PROPERTY(gamepads, get_gamepads); + private: LightMutex _lock; @@ -53,8 +63,16 @@ private: DevicesByPath _devices_by_path; #endif - typedef pvector InputDevices; - InputDevices _all_devices; +#ifdef _WIN32 + // There are always exactly four of these in existence. + XInputDevice _xinput_device0; + XInputDevice _xinput_device1; + XInputDevice _xinput_device2; + XInputDevice _xinput_device3; + double _last_detection; +#endif + + InputDeviceSet _connected_devices; static InputDeviceManager *_global_ptr; }; diff --git a/panda/src/device/inputDeviceSet.I b/panda/src/device/inputDeviceSet.I new file mode 100755 index 0000000000..ea12582570 --- /dev/null +++ b/panda/src/device/inputDeviceSet.I @@ -0,0 +1,44 @@ +// Filename: inputDeviceSet.I +// Created by: rdb (16Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE InputDeviceSet:: +~InputDeviceSet() { +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::operator [] +// Access: Published +// Description: Returns the nth InputDevice in the collection. +//////////////////////////////////////////////////////////////////// +INLINE InputDevice *InputDeviceSet:: +operator [] (size_t index) const { + nassertr(index < _devices.size(), NULL); + return _devices[index]; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::size +// Access: Published +// Description: Returns the number of devices in the collection. +//////////////////////////////////////////////////////////////////// +INLINE size_t InputDeviceSet:: +size() const { + return _devices.size(); +} diff --git a/panda/src/device/inputDeviceSet.cxx b/panda/src/device/inputDeviceSet.cxx new file mode 100755 index 0000000000..dec094a957 --- /dev/null +++ b/panda/src/device/inputDeviceSet.cxx @@ -0,0 +1,170 @@ +// Filename: inputDeviceSet.cxx +// Created by: rdb (16Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "inputDeviceSet.h" +#include "indent.h" + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +InputDeviceSet:: +InputDeviceSet() { +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::Copy Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +InputDeviceSet:: +InputDeviceSet(const InputDeviceSet ©) : + _devices(copy._devices) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::Copy Assignment Operator +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +operator = (const InputDeviceSet ©) { + _devices = copy._devices; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::add_device +// Access: Public +// Description: Adds a new InputDevice to the collection. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +add_device(InputDevice *device) { + _devices.insert(device); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::remove_device +// Access: Public +// Description: Removes the indicated InputDevice from the collection. +// Returns true if the device was removed, false if it was +// not a member of the collection. +//////////////////////////////////////////////////////////////////// +bool InputDeviceSet:: +remove_device(InputDevice *device) { + return _devices.erase(device) > 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::add_devices_from +// Access: Public +// Description: Adds all the InputDevices indicated in the other +// collection to this device. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +add_devices_from(const InputDeviceSet &other) { + size_t other_num_devices = other.size(); + for (size_t i = 0; i < other_num_devices; i++) { + _devices.push_back(other[i]); + } + _devices.sort(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::remove_devices_from +// Access: Public +// Description: Removes from this collection all of the InputDevices +// listed in the other collection. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +remove_devices_from(const InputDeviceSet &other) { + InputDevices new_devices; + InputDevices::const_iterator it; + for (it = _devices.begin(); it != _devices.end(); ++it) { + if (!other.has_device(*it)) { + new_devices.push_back(*it); + } + } + new_devices.sort(); + _devices.swap(new_devices); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::has_device +// Access: Public +// Description: Returns true if the indicated InputDevice appears in +// this collection, false otherwise. +//////////////////////////////////////////////////////////////////// +bool InputDeviceSet:: +has_device(InputDevice *device) const { + InputDevices::const_iterator it = _devices.find(device); + return (it != _devices.end()); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::clear +// Access: Published +// Description: Removes all InputDevices from the collection. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +clear() { + _devices.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::reserve +// Access: Published +// Description: This is a hint to Panda to allocate enough memory +// to hold the given number of NodePaths, if you know +// ahead of time how many you will be adding. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +reserve(size_t num) { + _devices.reserve(num); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::output +// Access: Published +// Description: Writes a brief one-line description of the +// InputDeviceSet to the indicated output stream. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +output(ostream &out) const { + if (_devices.size() == 1) { + out << "1 input device"; + } else { + out << _devices.size() << " input devices"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::write +// Access: Published +// Description: Writes a complete multi-line description of the +// InputDeviceSet to the indicated output stream. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +write(ostream &out, int indent_level) const { + output(indent(out, indent_level)); + out << ":\n"; + indent_level += 2; + + InputDevices::const_iterator it; + for (it = _devices.begin(); it != _devices.end(); ++it) { + (*it)->output(indent(out, indent_level)); + out << '\n'; + } +} diff --git a/panda/src/device/inputDeviceSet.h b/panda/src/device/inputDeviceSet.h new file mode 100755 index 0000000000..be9196752a --- /dev/null +++ b/panda/src/device/inputDeviceSet.h @@ -0,0 +1,67 @@ +// Filename: inputDeviceSet.h +// Created by: rdb (16Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef INPUTDEVICESET_H +#define INPUTDEVICESET_H + +#include "pandabase.h" +#include "ordered_vector.h" +#include "inputDevice.h" + +//////////////////////////////////////////////////////////////////// +// Class : InputDeviceSet +// Description : Manages a list of InputDevice objects, as returned +// by various InputDeviceManager methods. This is +// implemented like a set, meaning the same device +// cannot occur more than once. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE InputDeviceSet { +PUBLISHED: + InputDeviceSet(); + InputDeviceSet(const InputDeviceSet ©); + void operator = (const InputDeviceSet ©); + INLINE ~InputDeviceSet(); + +public: + void add_device(InputDevice *device); + bool remove_device(InputDevice *device); + void add_devices_from(const InputDeviceSet &other); + void remove_devices_from(const InputDeviceSet &other); + bool has_device(InputDevice *device) const; + +PUBLISHED: + void clear(); + void reserve(size_t num); + + INLINE InputDevice *operator [] (size_t index) const; + INLINE size_t size() const; + + void output(ostream &out) const; + void write(ostream &out, int indent_level = 0) const; + +private: + // This is currently implemented as ov_set instead of a regular set + // so that we can still support random access. + typedef ov_set InputDevices; + InputDevices _devices; +}; + +INLINE ostream &operator << (ostream &out, const InputDeviceSet &col) { + col.output(out); + return out; +} + +#include "inputDeviceSet.I" + +#endif diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 80fa832775..2e541a6f47 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -8,4 +8,5 @@ #include "evdevInputDevice.cxx" #include "inputDevice.cxx" #include "inputDeviceManager.cxx" +#include "inputDeviceSet.cxx" #include "linuxJoystickDevice.cxx" diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 13a2ffdc19..459d5703d1 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -16,6 +16,8 @@ #ifdef _WIN32 #include "gamepadButton.h" +#include "inputDeviceManager.h" +#include "string_utils.h" #include #include @@ -89,34 +91,166 @@ XInputDevice(DWORD user_index) : nassertv(init_xinput()); } - if (get_capabilities != NULL) { - XINPUT_CAPABILITIES caps; - if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS) { - _is_connected = true; - } else { - _is_connected = false; + _name = "XInput Device #"; + _name += format_string(user_index); + + _controls.resize(6); + _buttons.resize(16); + + set_control_map(0, C_left_trigger); + set_control_map(1, C_right_trigger); + set_control_map(2, C_left_x); + set_control_map(3, C_left_y); + set_control_map(4, C_right_x); + set_control_map(5, C_right_y); + + set_button_map(0, GamepadButton::dpad_up()); + set_button_map(1, GamepadButton::dpad_down()); + set_button_map(2, GamepadButton::dpad_left()); + set_button_map(3, GamepadButton::dpad_right()); + set_button_map(4, GamepadButton::start()); + set_button_map(5, GamepadButton::back()); + set_button_map(6, GamepadButton::lstick()); + set_button_map(7, GamepadButton::rstick()); + set_button_map(8, GamepadButton::lshoulder()); + set_button_map(9, GamepadButton::rshoulder()); + set_button_map(10, GamepadButton::guide()); + set_button_map(11, GamepadButton::action_a()); + set_button_map(12, GamepadButton::action_b()); + set_button_map(13, GamepadButton::action_x()); + set_button_map(14, GamepadButton::action_y()); + + // Check if the device is connected. If so, initialize it. + XINPUT_CAPABILITIES caps; + XINPUT_STATE state; + if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS && + get_state(_index, &state) == ERROR_SUCCESS) { + _is_connected = true; + init_device(caps, state); + } else { + _is_connected = false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::detect +// Access: Public +// Description: Called periodically by the InputDeviceManager to +// detect whether the device is currently connected. +//////////////////////////////////////////////////////////////////// +void XInputDevice:: +detect(InputDeviceManager *mgr) { + bool connected = false; + + XINPUT_CAPABILITIES caps; + XINPUT_STATE state; + if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS && + get_state(_index, &state) == ERROR_SUCCESS) { + connected = true; + } else { + connected = false; + } + + LightMutexHolder holder(_lock); + if (connected == _is_connected) { + // Nothing changed. + return; + } + _is_connected = connected; + + if (connected) { + init_device(caps, state); + mgr->add_device(this); + } else { + mgr->remove_device(this); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::init_xinput +// Access: Private, Static +// Description: Initializes the XInput library. +//////////////////////////////////////////////////////////////////// +bool XInputDevice:: +init_xinput() { + if (device_cat.is_debug()) { + device_cat.debug() << "Initializing XInput library.\n"; + } + + _initialized = true; + const char *dll_name = "Xinput1_4.dll"; + HMODULE module = LoadLibraryA(dll_name); + + // If we didn't find XInput 1.4, fall back to the older 1.3 version. + if (!module) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Xinput1_4.dll not found, falling back to Xinput1_3.dll\n"; } - if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { - _device_class = DC_gamepad; - } else { - _device_class = DC_unknown; + dll_name = "Xinput1_3.dll"; + module = LoadLibraryA(dll_name); + } + + if (module) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Successfully loaded " << dll_name << "\n"; } - if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { - _flags |= IDF_has_vibration; + // Undocumented version (XInputGetStateEx) that includes a + // state bit for the guide button. + get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100)); + if (get_state == NULL) { + get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState"); + if (get_state == NULL) { + device_cat.error() + << "Failed to find function XInputGetState in " << dll_name << ".\n"; + return false; + } } + + get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); + if (get_capabilities == NULL) { + device_cat.error() + << "Failed to find function XInputGetCapabilities in " << dll_name << ".\n"; + return false; + } + + get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation"); + get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104)); + return true; + } + + device_cat.error() + << "Failed to load Xinput1_4.dll or Xinput1_3.dll.\n"; + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::init_device +// Access: Private +// Description: Initializes the device. Called when the device was +// just connected. +//////////////////////////////////////////////////////////////////// +void XInputDevice:: +init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state) { + if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { + _device_class = DC_gamepad; + } else { + _device_class = DC_unknown; + } + + if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { + _flags |= IDF_has_vibration; } if (get_battery_information != NULL) { XINPUT_BATTERY_INFORMATION batt; if (get_battery_information(_index, 0, &batt) == ERROR_SUCCESS) { - if (batt.BatteryType == BATTERY_TYPE_DISCONNECTED) { - _is_connected = false; - - } else if (batt.BatteryType != BATTERY_TYPE_WIRED) { + if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED && + batt.BatteryType != BATTERY_TYPE_WIRED) { // This device has a battery. Report the battery level. - _is_connected = true; _flags |= IDF_has_battery; _battery_level = batt.BatteryLevel; _max_battery_level = BATTERY_LEVEL_FULL; @@ -163,105 +297,31 @@ XInputDevice(DWORD user_index) : _manufacturer.assign(buffer); } } - } else { - // We need something to name it. - _name = "XInput Device"; - } - - _controls.resize(6); - _buttons.resize(16); - - // Get the initial state. - XINPUT_STATE state; - if (get_state(_index, &state) != ERROR_SUCCESS) { - // Device was disconnected. - _is_connected = false; - return; } WORD buttons = state.Gamepad.wButtons; + WORD mask = 1; + for (int i = 0; i < 16; ++i) { + if (buttons & mask) { + // Set the state without triggering a button event. + _buttons[i]._state = (buttons & mask) ? S_down : S_up; + } + mask <<= 1; + if (i == 10) { + // XInput skips 0x0800. + mask <<= 1; + } + } - set_control_map(0, C_left_trigger); set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); - set_control_map(1, C_right_trigger); set_control_state(1, state.Gamepad.bRightTrigger / 255.0); - set_control_map(2, C_left_x); set_control_state(2, state.Gamepad.sThumbLX / 32767.0); - set_control_map(3, C_left_y); set_control_state(3, state.Gamepad.sThumbLY / 32767.0); - set_control_map(4, C_right_x); set_control_state(4, state.Gamepad.sThumbRX / 32767.0); - set_control_map(5, C_right_y); set_control_state(5, state.Gamepad.sThumbRY / 32767.0); - set_button_map(0, GamepadButton::dpad_up()); - set_button_state(0, (buttons & XINPUT_GAMEPAD_DPAD_UP) != 0); - set_button_map(1, GamepadButton::dpad_down()); - set_button_state(1, (buttons & XINPUT_GAMEPAD_DPAD_DOWN) != 0); - set_button_map(2, GamepadButton::dpad_left()); - set_button_state(2, (buttons & XINPUT_GAMEPAD_DPAD_LEFT) != 0); - set_button_map(3, GamepadButton::dpad_right()); - set_button_state(3, (buttons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0); - set_button_map(4, GamepadButton::start()); - set_button_state(4, (buttons & XINPUT_GAMEPAD_START) != 0); - set_button_map(5, GamepadButton::back()); - set_button_state(5, (buttons & XINPUT_GAMEPAD_BACK) != 0); - set_button_map(6, GamepadButton::lstick()); - set_button_state(6, (buttons & XINPUT_GAMEPAD_LEFT_THUMB) != 0); - set_button_map(7, GamepadButton::rstick()); - set_button_state(7, (buttons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0); - set_button_map(8, GamepadButton::lshoulder()); - set_button_state(8, (buttons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0); - set_button_map(9, GamepadButton::rshoulder()); - set_button_state(9, (buttons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0); - set_button_map(10, GamepadButton::guide()); - set_button_state(10, (buttons & 0x0400) != 0); - set_button_map(11, GamepadButton::action_a()); - set_button_state(11, (buttons & XINPUT_GAMEPAD_A) != 0); - set_button_map(12, GamepadButton::action_b()); - set_button_state(12, (buttons & XINPUT_GAMEPAD_B) != 0); - set_button_map(13, GamepadButton::action_x()); - set_button_state(13, (buttons & XINPUT_GAMEPAD_X) != 0); - set_button_map(14, GamepadButton::action_y()); - set_button_state(14, (buttons & XINPUT_GAMEPAD_Y) != 0); - _last_buttons = buttons; -} - -//////////////////////////////////////////////////////////////////// -// Function: XInputDevice::init -// Access: Protected, Static -// Description: Initializes the XInput library. -//////////////////////////////////////////////////////////////////// -bool XInputDevice:: -init_xinput() { - _initialized = true; - HMODULE module = LoadLibraryA("Xinput1_4.dll"); - if (!module) { - module = LoadLibraryA("Xinput1_3.dll"); - } - if (module) { - // Undocumented version (XInputGetStateEx) that includes a - // state bit for the guide button. - get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100)); - if (get_state == NULL) { - get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState"); - if (get_state == NULL) { - device_cat.error() - << "Failed to find function XInputGetState in XInput DLL.\n"; - return false; - } - } - - get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); - get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation"); - get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104)); - return true; - } - - device_cat.error() - << "Failed to load XInput DLL.\n"; - return false; + _last_packet = state.dwPacketNumber; } //////////////////////////////////////////////////////////////////// @@ -279,10 +339,24 @@ do_poll() { if (get_state(_index, &state) != ERROR_SUCCESS) { // Device was disconnected. - _is_connected = false; + if (_is_connected) { + _is_connected = false; + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->remove_device(this); + } return; - } else { - _is_connected = true; + + } else if (!_is_connected) { + // Device was (re)connected. It's a bit strange to call poll() on + // disconnected device, but there's nothing stopping the user from + // doing so. + XINPUT_CAPABILITIES caps; + if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS) { + _is_connected = true; + init_device(caps, state); + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->add_device(this); + } } if (state.dwPacketNumber == _last_packet) { @@ -299,6 +373,7 @@ do_poll() { } mask <<= 1; if (i == 10) { + // XInput skips 0x0800. mask <<= 1; } } diff --git a/panda/src/device/xInputDevice.h b/panda/src/device/xInputDevice.h index 7abc0eafc5..fea8cd082f 100644 --- a/panda/src/device/xInputDevice.h +++ b/panda/src/device/xInputDevice.h @@ -20,29 +20,33 @@ #ifdef _WIN32 +class InputDeviceManager; + +typedef struct _XINPUT_CAPABILITIES XINPUT_CAPABILITIES; +typedef struct _XINPUT_STATE XINPUT_STATE; + //////////////////////////////////////////////////////////////////// // Class : XInputDevice // Description : This uses Microsoft's XInput library to interface // with an Xbox 360 game controller. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA_DEVICE XInputDevice : public InputDevice { -PUBLISHED: +class EXPCL_PANDA_DEVICE XInputDevice FINAL : public InputDevice { +public: XInputDevice(DWORD user_index); + void detect(InputDeviceManager *mgr); static bool init_xinput(); -public: +private: + void init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state); virtual void do_poll(); private: - DWORD _index; + const DWORD _index; DWORD _last_packet; WORD _last_buttons; static bool _initialized; - - // There are only four of these in existence. - //static XInputDevice _devices[4]; }; #endif // _WIN32 From 307665891d007fdc6894cb5781bc3bc42ea28166 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 17 Dec 2015 01:45:23 +0100 Subject: [PATCH 07/82] Fix compile error on Linux in hotplug code --- panda/src/device/inputDeviceManager.cxx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 44b038adda..0e5907fa3f 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -266,10 +266,8 @@ update() { PT(InputDevice) device = it->second; device->set_connected(false); - InputDevices::iterator it2; _devices_by_path.erase(it); - it2 = std::find(_connected_devices.begin(), _connected_devices.end(), device); - _connected_devices.erase(it2); + _connected_devices.remove_device(device); if (device_cat.is_info()) { device_cat.info() From 70370999c919868849de5a2d31655121eaed9294 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 18 Dec 2015 16:13:50 +0100 Subject: [PATCH 08/82] Map XInput thumbsticks exactly to -1..1 range --- panda/src/device/xInputDevice.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 459d5703d1..227c5a4b01 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -380,10 +380,10 @@ do_poll() { set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); set_control_state(1, state.Gamepad.bRightTrigger / 255.0); - set_control_state(2, state.Gamepad.sThumbLX / 32767.0); - set_control_state(3, state.Gamepad.sThumbLY / 32767.0); - set_control_state(4, state.Gamepad.sThumbRX / 32767.0); - set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + set_control_state(2, (state.Gamepad.sThumbLX + 0.5) / 32767.5); + set_control_state(3, (state.Gamepad.sThumbLY + 0.5) / 32767.5); + set_control_state(4, (state.Gamepad.sThumbRX + 0.5) / 32767.5); + set_control_state(5, (state.Gamepad.sThumbRY + 0.5) / 32767.5); _last_buttons = state.Gamepad.wButtons; _last_packet = state.dwPacketNumber; From ab66108661fc5ea6a94b63fed80562ea72c8439e Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 19 Dec 2015 16:03:28 +0100 Subject: [PATCH 09/82] Robustify Linux gamepad support --- panda/src/device/evdevInputDevice.cxx | 196 +++++++++++++---- panda/src/device/evdevInputDevice.h | 14 +- panda/src/device/inputDevice.I | 39 +++- panda/src/device/inputDevice.h | 16 +- panda/src/device/inputDeviceManager.cxx | 260 ++++++++++++++++------- panda/src/device/inputDeviceManager.h | 8 +- panda/src/device/linuxJoystickDevice.cxx | 109 ++++++++-- panda/src/device/linuxJoystickDevice.h | 8 +- 8 files changed, 510 insertions(+), 140 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 9b69ead27e..191edabbc1 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -19,6 +19,7 @@ #include "gamepadButton.h" #include "keyboardButton.h" #include "mouseButton.h" +#include "inputDeviceManager.h" #include #include @@ -32,19 +33,6 @@ static InputDevice::ControlAxis axis_map[] = { TypeHandle EvdevInputDevice::_type_handle; -//////////////////////////////////////////////////////////////////// -// Function: EvdevInputDevice::Constructor -// Access: Published -// Description: Creates a new device using the Linux joystick -// device using the given file descriptor. It will -// be closed when this object destructs. -//////////////////////////////////////////////////////////////////// -EvdevInputDevice:: -EvdevInputDevice(int fd) : - _fd(fd) { - init_device(); -} - //////////////////////////////////////////////////////////////////// // Function: EvdevInputDevice::Constructor // Access: Published @@ -52,15 +40,18 @@ EvdevInputDevice(int fd) : // device using the given device filename. //////////////////////////////////////////////////////////////////// EvdevInputDevice:: -EvdevInputDevice(const string &fn) { - _fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK); +EvdevInputDevice(int index) : _index(index) { + char path[64]; + sprintf(path, "/dev/input/event%d", index); + + _fd = open(path, O_RDONLY | O_NONBLOCK); if (_fd >= 0) { init_device(); } else { _is_connected = false; device_cat.error() - << "Opening raw input device: " << strerror(errno) << " " << fn << "\n"; + << "Opening raw input device: " << strerror(errno) << " " << path << "\n"; } } @@ -77,6 +68,18 @@ EvdevInputDevice:: } } +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::check_events +// Access: Public +// Description: Returns true if there are pending events. +//////////////////////////////////////////////////////////////////// +bool EvdevInputDevice:: +check_events() const { + unsigned int avail = 0; + ioctl(_fd, FIONREAD, &avail); + return (avail != 0); +} + //////////////////////////////////////////////////////////////////// // Function: EvdevInputDevice::do_poll // Access: Private, Virtual @@ -88,8 +91,15 @@ EvdevInputDevice:: //////////////////////////////////////////////////////////////////// void EvdevInputDevice:: do_poll() { - if (_fd != -1) { + if (_fd != -1 && process_events()) { while (process_events()) {} + + // If we got events, we are obviously connected. Mark us so. + if (!_is_connected) { + _is_connected = true; + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->add_device(this); + } } } @@ -104,10 +114,8 @@ init_device() { uint8_t evtypes[(EV_CNT + 7) >> 3]; memset(evtypes, 0, sizeof(evtypes)); - char name[256]; - char uniq[256]; + char name[128]; if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || - ioctl(_fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0 || ioctl(_fd, EVIOCGBIT(0, sizeof(evtypes)), evtypes) < 0) { close(_fd); _fd = -1; @@ -116,29 +124,59 @@ init_device() { return false; } - for (char *p=name; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } - } - for (char *p=uniq; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } + _name.assign(name); + + struct input_id id; + if (ioctl(_fd, EVIOCGID, &id) >= 0) { + _vendor_id = id.vendor; + _product_id = id.product; } - _name = ((string)name) + "." + uniq; - + bool all_values_zero = true; if (test_bit(EV_ABS, evtypes)) { // Check which axes are on the device. uint8_t axes[(ABS_CNT + 7) >> 3]; memset(axes, 0, sizeof(axes)); + AxisRange range; + range._scale = 1.0; + range._bias = 0.0; + _axis_ranges.resize(ABS_CNT, range); + int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { if (i >= 0 && i < 6) { set_control_map(i, axis_map[i]); + + // Check the initial value and ranges. + struct input_absinfo absinfo; + if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { + double factor, bias; + if (absinfo.minimum < 0) { + // Centered, eg. for sticks. + factor = 2.0 / (absinfo.maximum - absinfo.minimum); + bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum); + } else { + // 0-based, eg. for triggers. + factor = 1.0 / absinfo.maximum; + bias = 0.0; + } + + // Flip Y axis to match Windows implementation. + if (i == ABS_Y || i == ABS_RY) { + factor = -factor; + bias = -bias; + } + + _axis_ranges[i]._scale = factor; + _axis_ranges[i]._bias = bias; + _controls[i]._state = fma(absinfo.value, factor, bias); + + if (absinfo.value != 0) { + all_values_zero = false; + } + } } } } @@ -148,14 +186,40 @@ init_device() { // Check which buttons are on the device. uint8_t keys[(KEY_CNT + 7) >> 3]; memset(keys, 0, sizeof(keys)); + ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys); + + // Also check whether the buttons are currently pressed. + uint8_t states[(KEY_CNT + 7) >> 3]; + memset(states, 0, sizeof(states)); + ioctl(_fd, EVIOCGKEY(sizeof(states)), states); - int num_bits = ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) << 3; int bi = 0; - for (int i = 0; i < num_bits; ++i) { + for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { - set_button_map(bi++, map_button(i)); + set_button_map(bi, map_button(i)); + if (test_bit(i, states)) { + _buttons[bi]._state = S_down; + all_values_zero = false; + } else { + _buttons[bi]._state = S_up; + } + ++bi; } } + + // Check device type. + if (test_bit(BTN_GAMEPAD, keys)) { + _device_class = DC_gamepad; + + } else if (test_bit(BTN_JOYSTICK, keys)) { + _device_class = DC_flight_stick; + + } else if (test_bit(BTN_MOUSE, keys)) { + _device_class = DC_mouse; + + } else if (test_bit(BTN_WHEEL, keys)) { + _device_class = DC_steering_wheel; + } } if (test_bit(EV_REL, evtypes)) { @@ -166,14 +230,52 @@ init_device() { _flags |= IDF_has_vibration; } - _is_connected = true; + char path[64]; + char buffer[256]; + sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); + FILE *f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } + fclose(f); + } + sprintf(path, "/sys/class/input/event%d/device/device/../manufacturer", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + fclose(f); + } + sprintf(path, "/sys/class/input/event%d/device/device/../serial", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + fclose(f); + } + + // Special-case fix for Xbox 360 Wireless Receiver: the Linux kernel + // driver always reports 4 connected gamepads, regardless of the number + // of gamepads actually present. This hack partially remedies this. + if (all_values_zero && _vendor_id == 0x045e && _product_id == 0x0719) { + _is_connected = false; + } else { + _is_connected = true; + } return true; } //////////////////////////////////////////////////////////////////// // Function: EvdevInputDevice::process_events // Access: Private -// Description: Reads a number of events from the device. +// Description: Reads a number of events from the device. Returns +// true if events were read, meaning this function +// should keep being called until it returns false. //////////////////////////////////////////////////////////////////// bool EvdevInputDevice:: process_events() { @@ -186,10 +288,12 @@ process_events() { // No data available for now. } else if (errno == ENODEV || errno == EINVAL) { - // The device ceased to exist, so we better close it. + // The device ceased to exist, so we better close it. No need + // to worry about removing it from the InputDeviceManager, as it + // will get an inotify event sooner or later about this. close(_fd); _fd = -1; - _is_connected = false; + //_is_connected = false; errno = 0; } else { @@ -210,23 +314,31 @@ process_events() { double time = ClockObject::get_global_clock()->get_frame_time(); ButtonHandle button; + // It seems that some devices send a single EV_SYN event when being + // unplugged. Boo. Ignore it. + if (n_read == 1 && events[0].code == EV_SYN) { + return false; + } + for (int i = 0; i < n_read; ++i) { + int code = events[i].code; + switch (events[i].type) { case EV_SYN: break; case EV_REL: - if (events[i].code == REL_X) x += events[i].value; - if (events[i].code == REL_Y) y += events[i].value; + if (code == REL_X) x += events[i].value; + if (code == REL_Y) y += events[i].value; have_pointer = true; break; case EV_ABS: - set_control_state(events[i].code, events[i].value / 32767.0); + set_control_state(code, fma(events[i].value, _axis_ranges[code]._scale, _axis_ranges[code]._bias)); break; case EV_KEY: - button = map_button(events[i].code); + button = map_button(code); _button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time)); break; diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index d3d4203625..a86a7d1434 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -25,11 +25,12 @@ // /dev/input/event# API to read data from a raw mouse. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { -PUBLISHED: - EvdevInputDevice(int fd); - EvdevInputDevice(const string &device); +public: + EvdevInputDevice(int index); virtual ~EvdevInputDevice(); + bool check_events() const; + private: virtual void do_poll(); @@ -37,8 +38,15 @@ private: bool process_events(); private: + int _index; int _fd; + struct AxisRange { + double _scale; + double _bias; + }; + pvector _axis_ranges; + public: static ButtonHandle map_button(int code); diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 8f04226896..5083e3fdda 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -35,7 +35,8 @@ InputDevice() : //////////////////////////////////////////////////////////////////// // Function: InputDevice::get_name // Access: Public -// Description: +// Description: Returns a human-readable name for the device. May +// not necessarily be unique. //////////////////////////////////////////////////////////////////// INLINE string InputDevice:: get_name() const { @@ -43,6 +44,42 @@ get_name() const { return _name; } +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_manufacturer +// Access: Public +// Description: Returns a string containing the manufacturer of +// the device, if this information is known. +//////////////////////////////////////////////////////////////////// +INLINE string InputDevice:: +get_manufacturer() const { + LightMutexHolder holder(_lock); + return _manufacturer; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_vendor_id +// Access: Public +// Description: Returns a string containing the USB vendor ID of +// the device, if this information is known. +//////////////////////////////////////////////////////////////////// +INLINE unsigned short InputDevice:: +get_vendor_id() const { + LightMutexHolder holder(_lock); + return _vendor_id; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_product_id +// Access: Public +// Description: Returns a string containing the USB product ID of +// the device, if this information is known. +//////////////////////////////////////////////////////////////////// +INLINE unsigned short InputDevice:: +get_product_id() const { + LightMutexHolder holder(_lock); + return _product_id; +} + //////////////////////////////////////////////////////////////////// // Function: InputDevice::is_connected // Access: Public diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index a6aaa5a015..5e33115a3e 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -111,12 +111,24 @@ PUBLISHED: }; INLINE string get_name() const; + INLINE string get_manufacturer() const; + INLINE unsigned short get_vendor_id() const; + INLINE unsigned short get_product_id() const; INLINE bool is_connected() const; INLINE DeviceClass get_device_class() const; // The human-readable name of this input device. MAKE_PROPERTY(name, get_name); + // The device's manufacturer, or the empty string if not known. + MAKE_PROPERTY(manufacturer, get_manufacturer); + + // USB vendor ID of the device, or 0 if not known. + MAKE_PROPERTY(vendor_id, get_vendor_id); + + // USB product ID of the device, or 0 if not known. + MAKE_PROPERTY(product_id, get_product_id); + // This is false if we know that the device is not currently connected. // May report false positives if we can't know this with certainty. MAKE_PROPERTY(connected, is_connected); @@ -212,8 +224,8 @@ protected: DeviceClass _device_class; int _flags; int _event_sequence; - short _vendor_id; - short _product_id; + unsigned short _vendor_id; + unsigned short _product_id; bool _is_connected; bool _enable_pointer_events; PointerData _pointer_data; diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 0e5907fa3f..e160a70746 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -39,33 +39,6 @@ InputDeviceManager *InputDeviceManager::_global_ptr = NULL; #ifdef PHAVE_LINUX_INPUT_H InputDeviceManager:: InputDeviceManager() : _inotify_fd(-1) { - - // Scan /dev/input for a list of input devices. - DIR *dir = opendir("/dev/input"); - if (dir) { - dirent *entry = readdir(dir); - while (entry != NULL) { - if (entry->d_type == DT_CHR) { - string name(entry->d_name); - if (!consider_add_linux_device(name)) { - // We can't access it. That's pretty normal for most devices. - if (device_cat.is_debug()) { - device_cat.debug() - << "Ignoring input device /dev/input/" << name << ": " - << strerror(errno) << "\n"; - } - errno = 0; - } - } - entry = readdir(dir); - } - closedir(dir); - } else { - device_cat.error() - << "Error opening directory /dev/input: " << strerror(errno) << "\n"; - return; - } - // Use inotify to watch /dev/input for hotplugging of devices. _inotify_fd = inotify_init1(O_NONBLOCK); @@ -77,6 +50,35 @@ InputDeviceManager() : _inotify_fd(-1) { device_cat.error() << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; } + + // Scan /dev/input for a list of input devices. + DIR *dir = opendir("/dev/input"); + if (dir) { + vector_int indices; + dirent *entry = readdir(dir); + while (entry != NULL) { + int index = -1; + if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) { + indices.push_back(index); + } + entry = readdir(dir); + } + closedir(dir); + + // We'll want to sort the devices by index, since the order may be + // meaningful (eg. for the Xbox wireless receiver). + std::sort(indices.begin(), indices.end()); + _evdev_devices.resize(indices.back() + 1, NULL); + + vector_int::const_iterator it; + for (it = indices.begin(); it != indices.end(); ++it) { + consider_add_evdev_device(*it); + } + } else { + device_cat.error() + << "Error opening directory /dev/input: " << strerror(errno) << "\n"; + return; + } } #elif defined(_WIN32) InputDeviceManager:: @@ -129,41 +131,124 @@ InputDeviceManager:: #ifdef PHAVE_LINUX_INPUT_H //////////////////////////////////////////////////////////////////// -// Function: InputDeviceManager::consider_add_linux_device +// Function: InputDeviceManager::consider_add_evdev_device // Access: Private -// Description: Checks whether the given device is accessible, and -// if so, adds it. Returns false on error. +// Description: Checks whether the event device with the given index +// is accessible, and if so, adds it. Returns the +// device if it was newly connected. //////////////////////////////////////////////////////////////////// -bool InputDeviceManager:: -consider_add_linux_device(const string &name) { - // Get the full path name first. - string path = "/dev/input/"; - path += name; - - if (access(path.c_str(), R_OK) < 0) { - return false; +InputDevice *InputDeviceManager:: +consider_add_evdev_device(int ev_index) { + if (ev_index < _evdev_devices.size()) { + if (_evdev_devices[ev_index] != NULL) { + // We already have this device. FIXME: probe it and add it to the + // list of connected devices? + return NULL; + } + } else { + // Make room to store this index. + _evdev_devices.resize(ev_index + 1, NULL); } - if (_devices_by_path.count(name)) { - // We already have this device. - return true; - } + // Check if we can directly read the event device. + char path[64]; + sprintf(path, "/dev/input/event%d", ev_index); - // Check if it's a joystick or game controller device. - if (name.size() > 2 && name[0] == 'j' && name[1] == 's' && isdigit(name[2])) { - PT(InputDevice) device = new LinuxJoystickDevice(path); - if (device_cat.is_info()) { - device_cat.info() - << "Discovered input device " << *device << "\n"; + if (access(path, R_OK) == 0) { + PT(InputDevice) device = new EvdevInputDevice(ev_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered evdev input device " << *device << "\n"; } - _devices_by_path[name] = device; - _connected_devices.add_device(MOVE(device)); - return true; + _evdev_devices[ev_index] = device; + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return _evdev_devices[ev_index]; } - return true; + // Nope. The permissions haven't been configured to allow it. + // Check if this corresponds to a /dev/input/jsX interface, which has + // a better chance of having read permissions set, but doesn't export + // all of the features (notably, force feedback). + + // We do this by checking for a js# directory inside the sysfs + // device directory. + sprintf(path, "/sys/class/input/event%d/device", ev_index); + + DIR *dir = opendir(path); + if (dir == NULL) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Error opening directory " << path << ": " << strerror(errno) << "\n"; + } + return NULL; + } + + dirent *entry = readdir(dir); + while (entry != NULL) { + int js_index = -1; + if (sscanf(entry->d_name, "js%d", &js_index) == 1) { + // Yes, we fonud a corresponding js device. Try adding it. + closedir(dir); + + InputDevice *device = consider_add_js_device(js_index); + if (device != NULL && device_cat.is_warning()) { + // This seemed to work. Display a warning to the user indicating + // that they might want to configure udev properly. + device_cat.warning() + << "Some features of " << device->get_device_class() + << " device " << device->get_name() << " are not available due" + " to lack of read permissions on /dev/input/event" << ev_index + << ".\n"; + } + _evdev_devices[ev_index] = device; + return device; + } + entry = readdir(dir); + } + + closedir(dir); + return NULL; } + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::consider_add_evdev_device +// Access: Private +// Description: Checks whether the joystick device with the given +// index is accessible, and if so, adds it. Returns +// the device if it was newly connected. +//////////////////////////////////////////////////////////////////// +InputDevice *InputDeviceManager:: +consider_add_js_device(int js_index) { + char path[64]; + sprintf(path, "/dev/input/js%d", js_index); + + if (access(path, R_OK) == 0) { + PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(js_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered joydev input device " << *device << "\n"; + } + InputDevice *device_p = device.p(); + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return device_p; + } + + return NULL; +} + #endif //////////////////////////////////////////////////////////////////// @@ -199,6 +284,12 @@ add_device(InputDevice *device) { { LightMutexHolder holder(_lock); _connected_devices.add_device(device); + +#ifdef PHAVE_LINUX_INPUT_H + // If we had added it pending activity on the device, remove it + // from the list of inactive devices. + _inactive_devices.remove_device(device); +#endif } throw_event("connect-device", device); } @@ -229,9 +320,23 @@ remove_device(InputDevice *device) { void InputDeviceManager:: update() { #ifdef PHAVE_LINUX_INPUT_H + // Check for any devices that may be disconnected and need to be probed + // in order to see whether they have been reconnected. + InputDeviceSet inactive_devices; + { + LightMutexHolder holder(_lock); + inactive_devices = _inactive_devices; + } + for (size_t i = 0; i < inactive_devices.size(); ++i) { + InputDevice *device = inactive_devices[i]; + if (device != NULL && !device->is_connected()) { + device->poll(); + } + } + // We use inotify to tell us whether a device was added, removed, // or has changed permissions to allow us to access it. - unsigned int avail; + unsigned int avail = 0; ioctl(_inotify_fd, FIONREAD, &avail); if (avail == 0) { return; @@ -261,34 +366,39 @@ update() { if (event->mask & IN_DELETE) { // The device was deleted. If we have it, remove it. - DevicesByPath::iterator it = _devices_by_path.find(name); - if (it != _devices_by_path.end()) { - PT(InputDevice) device = it->second; - device->set_connected(false); - _devices_by_path.erase(it); - _connected_devices.remove_device(device); + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + // Check if we have this evdev device. If so, disconnect it. + if (index < _evdev_devices.size()) { + PT(InputDevice) device = _evdev_devices[index]; + if (device != NULL) { + _evdev_devices[index] = NULL; + _inactive_devices.remove_device(device); + if (_connected_devices.remove_device(device)) { + throw_event("disconnect-device", device.p()); + } - if (device_cat.is_info()) { - device_cat.info() - << "Removed input device " << *device << "\n"; + if (device_cat.is_debug()) { + device_cat.debug() + << "Removed input device " << *device << "\n"; + } + } } - throw_event("disconnect-device", device.p()); } } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { - // The device was created, or it was chmodded to be accessible. - DevicesByPath::iterator it = _devices_by_path.find(name); - if (it == _devices_by_path.end()) { - // We don't know about this device yet. - if (!consider_add_linux_device(name) && (event->mask & IN_CREATE) != 0) { - if (device_cat.is_debug()) { - device_cat.debug() - << "Ignoring input device /dev/input/" << name << ": " - << strerror(errno) << "\n"; - } + // The device was created, or it was chmodded to be accessible. We + // need to check for the latter since it seems that the device may + // get the IN_CREATE event before the driver gets the permissions + // set properly. + + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + InputDevice *device = consider_add_evdev_device(index); + if (device != NULL && device->is_connected()) { + throw_event("connect-device", device); } - errno = 0; } } @@ -301,6 +411,8 @@ update() { // check if it's connected every so often. Perhaps we can switch to // using RegisterDeviceNotification in the future. double time_now = ClockObject::get_global_clock()->get_real_time(); + LightMutexHolder holder(_update_lock); + if (time_now - _last_detection > xinput_detection_delay.get_value()) { // I've heard this can be quite slow if no device is detected. We // should probably move it to a thread. diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 3f2c2cea43..2c96a5876f 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -36,7 +36,8 @@ private: ~InputDeviceManager(); #ifdef PHAVE_LINUX_INPUT_H - bool consider_add_linux_device(const string &name); + InputDevice *consider_add_evdev_device(int index); + InputDevice *consider_add_js_device(int index); #endif public: @@ -59,12 +60,13 @@ private: #ifdef PHAVE_LINUX_INPUT_H int _inotify_fd; - typedef pmap DevicesByPath; - DevicesByPath _devices_by_path; + pvector _evdev_devices; + InputDeviceSet _inactive_devices; #endif #ifdef _WIN32 // There are always exactly four of these in existence. + LightMutex _update_lock; XInputDevice _xinput_device0; XInputDevice _xinput_device1; XInputDevice _xinput_device2; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index b3566554e6..f15b4cf1dd 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -30,14 +30,14 @@ TypeHandle LinuxJoystickDevice::_type_handle; // device using the given device filename. //////////////////////////////////////////////////////////////////// LinuxJoystickDevice:: -LinuxJoystickDevice(const string &device) : +LinuxJoystickDevice(int index) : _fd(-1), - _device(device) + _index(index) { LightMutexHolder holder(_lock); if (!open_device()) { device_cat.error() - << "Could not open joystick device " << _device + << "Could not open joystick device /dev/input/js" << index << ": " << strerror(errno) << "\n"; } } @@ -55,6 +55,18 @@ LinuxJoystickDevice:: } } +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::check_events +// Access: Public +// Description: Returns true if there are pending events. +//////////////////////////////////////////////////////////////////// +bool LinuxJoystickDevice:: +check_events() const { + unsigned int avail = 0; + ioctl(_fd, FIONREAD, &avail); + return (avail != 0); +} + //////////////////////////////////////////////////////////////////// // Function: LinuxJoystickDevice::do_poll // Access: Private, Virtual @@ -66,10 +78,15 @@ LinuxJoystickDevice:: //////////////////////////////////////////////////////////////////// void LinuxJoystickDevice:: do_poll() { - if (_fd != -1) { + if (_fd != -1 && process_events()) { while (process_events()) {} - } else { - open_device(); + + // If we got events, we are obviously connected. Mark us so. + if (!_is_connected) { + _is_connected = true; + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->add_device(this); + } } } @@ -84,14 +101,18 @@ bool LinuxJoystickDevice:: open_device() { nassertr(_lock.debug_is_locked(), false); - _fd = open(_device.c_str(), O_RDONLY | O_NONBLOCK); + char path[64]; + sprintf(path, "/dev/input/js%d", _index); + + _fd = open(path, O_RDONLY | O_NONBLOCK); if (_fd == -1) { _is_connected = false; return false; } - // Read the name from the device. + // Read the name from the device. We'll later try to use sysfs to read + // the proper product name from the device, but this is a good fallback. char name[128]; name[0] = 0; ioctl(_fd, JSIOCGNAME(sizeof(name)), name); @@ -249,16 +270,76 @@ open_device() { } } + // Get additional information from sysfs. + sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index); + FILE *f = fopen(path, "r"); + if (f) { + fscanf(f, "%hx", &_vendor_id); + fclose(f); + } + sprintf(path, "/sys/class/input/js%d/device/id/product", _index); + f = fopen(path, "r"); + if (f) { + fscanf(f, "%hx", &_product_id); + fclose(f); + } + char buffer[256]; + sprintf(path, "/sys/class/input/js%d/device/device/../product", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } + fclose(f); + } + sprintf(path, "/sys/class/input/js%d/device/device/../manufacturer", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + fclose(f); + } + sprintf(path, "/sys/class/input/js%d/device/device/../serial", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + fclose(f); + } + // Read the init events. - _is_connected = true; while (process_events()) {}; + + // Special case handling for the wireless Xbox receiver - the Linux + // joystick API doesn't seem to have a way to report whether the device + // is actually turned on. The best we can do is check whether the axes + // are all 0, which indicates that the driver hasn't received any data for + // this gamepad yet (which means it hasn't been plugged in for this session) + if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) { + for (int i = 0; i < _controls.size(); ++i) { + if (_controls[i]._state != 0.0) { + _is_connected = true; + return true; + } + } + _is_connected = false; + } else { + _is_connected = true; + } + return true; } //////////////////////////////////////////////////////////////////// // Function: LinuxJoystickDevice::process_events // Access: Private -// Description: Reads a number of events from the joystick. +// Description: Reads a number of events from the device. Returns +// true if events were read, meaning this function +// should keep being called until it returns false. //////////////////////////////////////////////////////////////////// bool LinuxJoystickDevice:: process_events() { @@ -271,10 +352,12 @@ process_events() { // No data available for now. } else if (errno == ENODEV) { - // The device ceased to exist, so we better close it. + // The device ceased to exist, so we better close it. No need + // to worry about removing it from the InputDeviceManager, as it + // will get an inotify event sooner or later about this. close(_fd); _fd = -1; - _is_connected = false; + //_is_connected = false; errno = 0; } else { @@ -301,6 +384,8 @@ process_events() { if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { // We'd like to use 0.0 to indicate the resting position. set_control_state(index, (events[i].value + 32767) / 65534.0); + } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { + set_control_state(index, events[i].value / -32767.0); } else { set_control_state(index, events[i].value / 32767.0); } diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index d71ad45c17..c2952d061f 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -25,10 +25,12 @@ // /dev/input/js# API to read data from a game controller. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice { -PUBLISHED: - LinuxJoystickDevice(const string &device); +public: + LinuxJoystickDevice(int index); virtual ~LinuxJoystickDevice(); + bool check_events() const; + private: virtual void do_poll(); @@ -37,7 +39,7 @@ private: private: int _fd; - string _device; + int _index; public: static TypeHandle get_class_type() { From 687e080bdd9e718021808a9d895540395096c215 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 19 Dec 2015 19:32:52 +0100 Subject: [PATCH 10/82] Add gamepad rumble support on Linux --- panda/src/device/evdevInputDevice.cxx | 102 +++++++++++++++++++++--- panda/src/device/evdevInputDevice.h | 8 +- panda/src/device/inputDevice.I | 15 ++++ panda/src/device/inputDevice.cxx | 12 +++ panda/src/device/inputDevice.h | 3 + panda/src/device/inputDeviceManager.cxx | 6 +- 6 files changed, 129 insertions(+), 17 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 191edabbc1..73e247462b 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -40,11 +40,25 @@ TypeHandle EvdevInputDevice::_type_handle; // device using the given device filename. //////////////////////////////////////////////////////////////////// EvdevInputDevice:: -EvdevInputDevice(int index) : _index(index) { +EvdevInputDevice(int index) : + _index(index), + _fd(-1), + _can_write(false), + _ff_id(-1), + _ff_playing(false), + _ff_strong(-1), + _ff_weak(-1) { + char path[64]; sprintf(path, "/dev/input/event%d", index); - _fd = open(path, O_RDONLY | O_NONBLOCK); + _fd = open(path, O_RDWR | O_NONBLOCK); + if (_fd >= 0) { + _can_write = true; + } else { + // On failure, open device as read-only. + _fd = open(path, O_RDONLY | O_NONBLOCK); + } if (_fd >= 0) { init_device(); @@ -63,21 +77,74 @@ EvdevInputDevice(int index) : _index(index) { EvdevInputDevice:: ~EvdevInputDevice() { if (_fd != -1) { + if (_ff_id != -1) { + // Remove force-feedback effect. + do_set_vibration(0, 0); + ioctl(_fd, EVIOCRMFF, _ff_id); + _ff_id = -1; + } + close(_fd); _fd = -1; } } //////////////////////////////////////////////////////////////////// -// Function: EvdevInputDevice::check_events -// Access: Public -// Description: Returns true if there are pending events. +// Function: EvdevInputDevice::do_set_vibration +// Access: Private, Virtual +// Description: Sets the vibration strength. The first argument +// controls a low-frequency motor, if present, and +// the latter controls a high-frequency motor. The +// values are within the 0-1 range. //////////////////////////////////////////////////////////////////// -bool EvdevInputDevice:: -check_events() const { - unsigned int avail = 0; - ioctl(_fd, FIONREAD, &avail); - return (avail != 0); +void EvdevInputDevice:: +do_set_vibration(double strong, double weak) { + if (_fd == -1 || !_can_write) { + return; + } + + int strong_level = strong * 0xffff; + int weak_level = weak * 0xffff; + + if (strong_level == _ff_strong && weak_level == _ff_weak) { + // No change. + return; + } + + // Upload the new effect parameters. Do this even if we are about + // to stop the effect, because some drivers don't respond to simply + // stopping the effect. + struct ff_effect effect; + effect.type = FF_RUMBLE; + effect.id = _ff_id; + effect.direction = 0; + effect.trigger.button = 0; + effect.trigger.interval = 0; + effect.replay.length = 0; + effect.replay.delay = 0; + effect.u.rumble.strong_magnitude = strong_level; + effect.u.rumble.weak_magnitude = weak_level; + + if (ioctl(_fd, EVIOCSFF, &effect) < 0) { + return; + } else { + _ff_id = effect.id; + _ff_strong = strong_level; + _ff_weak = weak_level; + } + + if (!_ff_playing) { + // Start the effect. We could pass 0 as value to stop the effect + // when a level of 0 is requested, but my driver seems to ignore it. + _ff_playing = true; + + struct input_event play; + play.type = EV_FF; + play.code = _ff_id; + play.value = 1; + + write(_fd, &play, sizeof(play)); + } } //////////////////////////////////////////////////////////////////// @@ -227,7 +294,20 @@ init_device() { } if (test_bit(EV_FF, evtypes)) { - _flags |= IDF_has_vibration; + uint8_t effects[(FF_CNT + 7) >> 3]; + memset(effects, 0, sizeof(effects)); + ioctl(_fd, EVIOCGBIT(EV_FF, sizeof(effects)), effects); + + if (test_bit(FF_RUMBLE, effects)) { + if (_can_write) { + _flags |= IDF_has_vibration; + } else { + // Let the user know what he's missing out on. + device_cat.warning() + << "/dev/input/event" << _index << " is not writable, vibration " + << "effects will be unavailable.\n"; + } + } } char path[64]; diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index a86a7d1434..6eff8534a5 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -29,9 +29,8 @@ public: EvdevInputDevice(int index); virtual ~EvdevInputDevice(); - bool check_events() const; - private: + virtual void do_set_vibration(double strong, double weak); virtual void do_poll(); bool init_device(); @@ -40,6 +39,11 @@ private: private: int _index; int _fd; + bool _can_write; + int _ff_id; + bool _ff_playing; + int _ff_strong; + int _ff_weak; struct AxisRange { double _scale; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 5083e3fdda..06e8d2227e 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -393,6 +393,21 @@ is_control_known(int index) const { } } +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_vibration +// Access: Public +// Description: Sets the strength of the vibration effect, if +// supported. The values are clamped to 0-1 range. +// The first value controls the low-frequency rumble +// motor, whereas the second controls the high-frequency +// motor, if present. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +set_vibration(double strong, double weak) { + LightMutexHolder holder(_lock); + do_set_vibration(max(min(strong, 1.0), 0.0), max(min(weak, 1.0), 0.0)); +} + //////////////////////////////////////////////////////////////////// // Function: InputDevice::enable_pointer_events // Access: Public diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 4637673672..0b4279e34e 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -417,6 +417,18 @@ write_controls(ostream &out, int indent_level) const { } } +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::do_set_vibration +// Access: Private, Virtual +// Description: Sets the vibration strength. The first argument +// controls a low-frequency motor, if present, and +// the latter controls a high-frequency motor. The +// values are within the 0-1 range. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +do_set_vibration(double strong, double weak) { +} + //////////////////////////////////////////////////////////////////// // Function: InputDevice::do_poll // Access: Protected, Virtual diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 5e33115a3e..99a8e059ca 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -160,6 +160,8 @@ PUBLISHED: INLINE double get_control_state(int index) const; INLINE bool is_control_known(int index) const; + INLINE void set_vibration(double strong, double weak); + INLINE void enable_pointer_events(); INLINE void disable_pointer_events(); @@ -179,6 +181,7 @@ protected: void set_control_state(int index, double state); void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); + virtual void do_set_vibration(double low, double high); virtual void do_poll(); public: diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index e160a70746..1b12786d62 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -202,10 +202,8 @@ consider_add_evdev_device(int ev_index) { // This seemed to work. Display a warning to the user indicating // that they might want to configure udev properly. device_cat.warning() - << "Some features of " << device->get_device_class() - << " device " << device->get_name() << " are not available due" - " to lack of read permissions on /dev/input/event" << ev_index - << ".\n"; + << "/dev/input/event" << ev_index << " is not readable, some " + "features will be unavailable.\n"; } _evdev_devices[ev_index] = device; return device; From bfea37f64d5fcf8f16a1eeae7139276cf99f028b Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 19 Dec 2015 20:09:07 +0100 Subject: [PATCH 11/82] Detect keyboard devices on Linux --- panda/src/device/evdevInputDevice.cxx | 7 +++++++ panda/src/device/inputDevice.cxx | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 73e247462b..3d7388f3bf 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -274,6 +274,10 @@ init_device() { } } + if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) { + _flags |= IDF_has_keyboard; + } + // Check device type. if (test_bit(BTN_GAMEPAD, keys)) { _device_class = DC_gamepad; @@ -286,6 +290,9 @@ init_device() { } else if (test_bit(BTN_WHEEL, keys)) { _device_class = DC_steering_wheel; + + } else if (_flags & IDF_has_keyboard) { + _device_class = DC_keyboard; } } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 0b4279e34e..f1e6a7fbb6 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -283,11 +283,17 @@ output(ostream &out) const { } if (_buttons.size() > 0) { - out << ", " << _buttons.size() << " buttons"; + out << ", " << _buttons.size() << " button"; + if (_buttons.size() != 1) { + out.put('s'); + } } if (_controls.size() > 0) { - out << ", " << _controls.size() << " controls"; + out << ", " << _controls.size() << " control"; + if (_controls.size() != 1) { + out.put('s'); + } } if (_flags & IDF_has_pointer) { From 44f9b2232361b75640d77195bed7342a1a9fb4a8 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 19 Dec 2015 21:04:49 +0100 Subject: [PATCH 12/82] Gamepad vibration on Windows --- dtool/src/parser-inc/XInput.h | 1 + panda/src/device/xInputDevice.cxx | 38 ++++++++++++++++++++++++++++++- panda/src/device/xInputDevice.h | 4 +++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/dtool/src/parser-inc/XInput.h b/dtool/src/parser-inc/XInput.h index f770cf3d71..21a2802287 100644 --- a/dtool/src/parser-inc/XInput.h +++ b/dtool/src/parser-inc/XInput.h @@ -1,2 +1,3 @@ struct XINPUT_STATE; struct XINPUT_CAPABILITIES; +struct XINPUT_VIBRATION; diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 227c5a4b01..290cfb12f3 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -63,11 +63,13 @@ struct XINPUT_BUSINFO { }; typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); +typedef DWORD (*pXInputSetState)(DWORD, XINPUT_VIBRATION *); typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *); static pXInputGetState get_state = NULL; +static pXInputSetState set_state = NULL; static pXInputGetCapabilities get_capabilities = NULL; static pXInputGetBatteryInformation get_battery_information = NULL; static pXInputGetBaseBusInformation get_base_bus_information = NULL; @@ -132,6 +134,17 @@ XInputDevice(DWORD user_index) : } } +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::Constructor +// Access: Published +// Description: Creates a new device using the Linux joystick +// device using the given device filename. +//////////////////////////////////////////////////////////////////// +XInputDevice:: +~XInputDevice() { + do_set_vibration(0, 0); +} + //////////////////////////////////////////////////////////////////// // Function: XInputDevice::detect // Access: Public @@ -210,6 +223,13 @@ init_xinput() { } } + set_state = (pXInputSetState)GetProcAddress(module, "XInputSetState"); + if (set_state == NULL) { + device_cat.error() + << "Failed to find function XInputSetState in " << dll_name << ".\n"; + return false; + } + get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); if (get_capabilities == NULL) { device_cat.error() @@ -234,7 +254,7 @@ init_xinput() { // just connected. //////////////////////////////////////////////////////////////////// void XInputDevice:: -init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state) { +init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { _device_class = DC_gamepad; } else { @@ -324,6 +344,22 @@ init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state) { _last_packet = state.dwPacketNumber; } +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::do_set_vibration +// Access: Private, Virtual +// Description: Sets the vibration strength. The first argument +// controls a low-frequency motor, if present, and +// the latter controls a high-frequency motor. The +// values are within the 0-1 range. +//////////////////////////////////////////////////////////////////// +void XInputDevice:: +do_set_vibration(double strong, double weak) { + XINPUT_VIBRATION vibration; + vibration.wLeftMotorSpeed = strong * 0xffff; + vibration.wRightMotorSpeed = weak * 0xffff; + set_state(_index, &vibration); +} + //////////////////////////////////////////////////////////////////// // Function: InputDevice::do_poll // Access: Public, Virtual diff --git a/panda/src/device/xInputDevice.h b/panda/src/device/xInputDevice.h index fea8cd082f..3bfc9fe728 100644 --- a/panda/src/device/xInputDevice.h +++ b/panda/src/device/xInputDevice.h @@ -33,12 +33,14 @@ typedef struct _XINPUT_STATE XINPUT_STATE; class EXPCL_PANDA_DEVICE XInputDevice FINAL : public InputDevice { public: XInputDevice(DWORD user_index); + ~XInputDevice(); void detect(InputDeviceManager *mgr); static bool init_xinput(); private: - void init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state); + void init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state); + virtual void do_set_vibration(double strong, double weak); virtual void do_poll(); private: From 01e57e1bdd77400050c44f55c602e4cd3d698389 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Jun 2016 10:54:03 +0200 Subject: [PATCH 13/82] Emulate d-pad from hat axes, fixes Xbox One d-pad buttons on Linux --- panda/src/device/evdevInputDevice.cxx | 126 ++++++++++++++--------- panda/src/device/evdevInputDevice.h | 6 ++ panda/src/device/inputDevice.I | 10 ++ panda/src/device/inputDevice.h | 1 + panda/src/device/linuxJoystickDevice.cxx | 50 ++++++++- panda/src/device/linuxJoystickDevice.h | 6 ++ 6 files changed, 147 insertions(+), 52 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index f3131d0f1d..ef318c3e5d 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -43,7 +43,11 @@ EvdevInputDevice(int index) : _ff_id(-1), _ff_playing(false), _ff_strong(-1), - _ff_weak(-1) { + _ff_weak(-1), + _dpad_x_axis(-1), + _dpad_y_axis(-1), + _dpad_left_button(-1), + _dpad_up_button(-1) { char path[64]; sprintf(path, "/dev/input/event%d", index); @@ -185,54 +189,7 @@ init_device() { } bool all_values_zero = true; - if (test_bit(EV_ABS, evtypes)) { - // Check which axes are on the device. - uint8_t axes[(ABS_CNT + 7) >> 3]; - memset(axes, 0, sizeof(axes)); - - AxisRange range; - range._scale = 1.0; - range._bias = 0.0; - _axis_ranges.resize(ABS_CNT, range); - - int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; - for (int i = 0; i < num_bits; ++i) { - if (test_bit(i, axes)) { - if (i >= 0 && i < 6) { - set_control_map(i, axis_map[i]); - - // Check the initial value and ranges. - struct input_absinfo absinfo; - if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { - double factor, bias; - if (absinfo.minimum < 0) { - // Centered, eg. for sticks. - factor = 2.0 / (absinfo.maximum - absinfo.minimum); - bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum); - } else { - // 0-based, eg. for triggers. - factor = 1.0 / absinfo.maximum; - bias = 0.0; - } - - // Flip Y axis to match Windows implementation. - if (i == ABS_Y || i == ABS_RY) { - factor = -factor; - bias = -bias; - } - - _axis_ranges[i]._scale = factor; - _axis_ranges[i]._bias = bias; - _controls[i]._state = fma(absinfo.value, factor, bias); - - if (absinfo.value != 0) { - all_values_zero = false; - } - } - } - } - } - } + bool have_dpad_buttons = false; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. @@ -255,6 +212,9 @@ init_device() { } else { _buttons[bi]._state = S_up; } + if (_buttons[bi]._handle == GamepadButton::dpad_left()) { + have_dpad_buttons = true; + } ++bi; } } @@ -281,6 +241,67 @@ init_device() { } } + if (test_bit(EV_ABS, evtypes)) { + // Check which axes are on the device. + uint8_t axes[(ABS_CNT + 7) >> 3]; + memset(axes, 0, sizeof(axes)); + + AxisRange range; + range._scale = 1.0; + range._bias = 0.0; + _axis_ranges.resize(ABS_CNT, range); + + int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; + for (int i = 0; i < num_bits; ++i) { + if (test_bit(i, axes)) { + set_control_map(i, axis_map[i]); + + // Check the initial value and ranges. + struct input_absinfo absinfo; + if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { + double factor, bias; + if (absinfo.minimum < 0) { + // Centered, eg. for sticks. + factor = 2.0 / (absinfo.maximum - absinfo.minimum); + bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum); + } else { + // 0-based, eg. for triggers. + factor = 1.0 / absinfo.maximum; + bias = 0.0; + } + + // Flip Y axis to match Windows implementation. + if (i == ABS_Y || i == ABS_RY) { + factor = -factor; + bias = -bias; + } + + _axis_ranges[i]._scale = factor; + _axis_ranges[i]._bias = bias; + _controls[i]._state = fma(absinfo.value, factor, bias); + + if (absinfo.value != 0) { + all_values_zero = false; + } + } + + // Emulate D-Pad buttons if necessary. + if (i == ABS_HAT0X && !have_dpad_buttons) { + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } + if (i == ABS_HAT0Y && !have_dpad_buttons) { + _dpad_y_axis = i; + _dpad_up_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } + } + } + } + if (test_bit(EV_REL, evtypes)) { _flags |= IDF_has_pointer; } @@ -403,6 +424,13 @@ process_events() { break; case EV_ABS: + if (code == _dpad_x_axis) { + set_button_state(_dpad_left_button, events[i].value < 0); + set_button_state(_dpad_left_button+1, events[i].value > 0); + } else if (code == _dpad_y_axis) { + set_button_state(_dpad_up_button, events[i].value < 0); + set_button_state(_dpad_up_button+1, events[i].value > 0); + } set_control_state(code, fma(events[i].value, _axis_ranges[code]._scale, _axis_ranges[code]._bias)); break; diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 772342a946..5a604b8617 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -50,6 +50,12 @@ private: }; pvector _axis_ranges; + // These are used for D-pad emulation. + int _dpad_x_axis; + int _dpad_y_axis; + int _dpad_left_button; + int _dpad_up_button; + public: static ButtonHandle map_button(int code); diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index d1c6cdf5eb..e63930984b 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -382,6 +382,16 @@ ButtonState() : { } +/** + * + */ +INLINE InputDevice::ButtonState:: +ButtonState(ButtonHandle handle) : + _handle(handle), + _state(S_unknown) +{ +} + /** * */ diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 889ffec2f8..0df79c3c65 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -238,6 +238,7 @@ public: class ButtonState { public: INLINE ButtonState(); + INLINE ButtonState(ButtonHandle handle); ButtonHandle _handle; State _state; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 826b4ac442..ed4e7fa329 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -28,7 +28,11 @@ TypeHandle LinuxJoystickDevice::_type_handle; LinuxJoystickDevice:: LinuxJoystickDevice(int index) : _fd(-1), - _index(index) + _index(index), + _dpad_x_axis(-1), + _dpad_y_axis(-1), + _dpad_left_button(-1), + _dpad_up_button(-1) { LightMutexHolder holder(_lock); if (!open_device()) { @@ -115,7 +119,7 @@ open_device() { uint16_t btnmap[512]; ioctl(_fd, JSIOCGBTNMAP, btnmap); - for (char i = 0; i < num_buttons; ++i) { + for (uint8_t i = 0; i < num_buttons; ++i) { ButtonHandle handle = ButtonHandle::none(); switch (btnmap[i]) { case BTN_A: @@ -181,6 +185,7 @@ open_device() { case BTN_TRIGGER_HAPPY1: handle = GamepadButton::dpad_left(); + _dpad_left_button = i; break; case BTN_TRIGGER_HAPPY2: @@ -189,6 +194,7 @@ open_device() { case BTN_TRIGGER_HAPPY3: handle = GamepadButton::dpad_up(); + _dpad_up_button = i; break; case BTN_TRIGGER_HAPPY4: @@ -196,6 +202,10 @@ open_device() { break; default: + if (device_cat.is_debug()) { + device_cat.debug() << "Unmapped /dev/input/js" << _index + << " button " << (int)i << ": 0x" << hex << btnmap[i] << "\n"; + } handle = ButtonHandle::none(); break; } @@ -207,7 +217,7 @@ open_device() { uint8_t axmap[512]; ioctl(_fd, JSIOCGAXMAP, axmap); - for (char i = 0; i < num_axes; ++i) { + for (uint8_t i = 0; i < num_axes; ++i) { ControlAxis axis = C_none; switch (axmap[i]) { @@ -247,7 +257,33 @@ open_device() { axis = C_right_trigger; break; + case ABS_HAT0X: + if (_dpad_left_button == -1) { + // Emulate D-Pad. + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } + axis = C_none; + break; + + case ABS_HAT0Y: + if (_dpad_up_button == -1) { + // Emulate D-Pad. + _dpad_y_axis = i; + _dpad_up_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } + axis = C_none; + break; + default: + if (device_cat.is_debug()) { + device_cat.debug() << "Unmapped /dev/input/js" << _index + << " axis " << (int)i << ": 0x" << hex << (int)axmap[i] << "\n"; + } axis = C_none; break; } @@ -362,6 +398,14 @@ process_events() { set_button_state(index, (events[i].value != 0)); } else if (events[i].type & JS_EVENT_AXIS) { + if (index == _dpad_x_axis) { + set_button_state(_dpad_left_button, events[i].value < -1000); + set_button_state(_dpad_left_button+1, events[i].value > 1000); + } else if (index == _dpad_y_axis) { + set_button_state(_dpad_up_button, events[i].value < -1000); + set_button_state(_dpad_up_button+1, events[i].value > 1000); + } + ControlAxis axis = _controls[index]._axis; if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index 0ec90919f8..6507d4d80a 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -39,6 +39,12 @@ private: int _fd; int _index; + // These are used for D-pad emulation. + int _dpad_x_axis; + int _dpad_y_axis; + int _dpad_left_button; + int _dpad_up_button; + public: static TypeHandle get_class_type() { return _type_handle; From fd16e346609bdff1d3ee17fd2f142e85b8fe0bf3 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Jun 2016 13:30:57 +0200 Subject: [PATCH 14/82] Fix Xbox One controller on Windows, add untested support for flight sticks and steering wheels --- panda/src/device/evdevInputDevice.cxx | 91 +++++++++++++---- panda/src/device/inputDevice.cxx | 20 +++- panda/src/device/inputDevice.h | 12 +++ panda/src/device/linuxJoystickDevice.cxx | 32 +++++- panda/src/device/xInputDevice.cxx | 125 +++++++++++++++++------ 5 files changed, 225 insertions(+), 55 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index ef318c3e5d..4aa94e023d 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -23,11 +23,54 @@ #include #include -#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7))) +#define test_bit(bit, array) ((array)[(bit)>>3] & (1<<((bit)&7))) static InputDevice::ControlAxis axis_map[] = { - InputDevice::C_left_x, InputDevice::C_left_y, InputDevice::C_left_trigger, - InputDevice::C_right_x, InputDevice::C_right_y, InputDevice::C_right_trigger + // ABS_X = 0x00 + InputDevice::C_left_x, + // ABS_Y = 0x01 + InputDevice::C_left_y, + // ABS_Z = 0x02 + InputDevice::C_left_trigger, + // ABS_RX = 0x03 + InputDevice::C_right_x, + // ABS_RY = 0x04 + InputDevice::C_right_y, + // ABS_RZ = 0x05 + InputDevice::C_right_trigger, + // ABS_THROTTLE = 0x06 + InputDevice::C_throttle, + // ABS_RUDDER = 0x07 + InputDevice::C_rudder, + // ABS_WHEEL = 0x08 + InputDevice::C_wheel, + // ABS_GAS = 0x09 + InputDevice::C_accelerator, + // ABS_BRAKE = 0x0a + InputDevice::C_brake, + + InputDevice::C_none, + InputDevice::C_none, + InputDevice::C_none, + InputDevice::C_none, + InputDevice::C_none, + + // ABS_HAT0X = 0x10 + InputDevice::C_hat_x, + // ABS_HAT0Y = 0x11 + InputDevice::C_hat_y, + + // ABS_HAT1X = 0x12 + // ABS_HAT1Y = 0x13 + // ABS_HAT2X = 0x14 + // ABS_HAT2Y = 0x15 + // ABS_HAT3X = 0x16 + // ABS_HAT3Y = 0x17 + // ABS_PRESSURE = 0x18 + // ABS_DISTANCE = 0x19 + // ABS_TILT_X = 0x1a + // ABS_TILT_Y = 0x1b + // ABS_TOOL_WIDTH = 0x1c }; TypeHandle EvdevInputDevice::_type_handle; @@ -189,7 +232,7 @@ init_device() { } bool all_values_zero = true; - bool have_dpad_buttons = false; + bool emulate_dpad = true; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. @@ -213,7 +256,7 @@ init_device() { _buttons[bi]._state = S_up; } if (_buttons[bi]._handle == GamepadButton::dpad_left()) { - have_dpad_buttons = true; + emulate_dpad = false; } ++bi; } @@ -241,6 +284,10 @@ init_device() { } } + if (_device_class != DC_gamepad) { + emulate_dpad = false; + } + if (test_bit(EV_ABS, evtypes)) { // Check which axes are on the device. uint8_t axes[(ABS_CNT + 7) >> 3]; @@ -254,7 +301,25 @@ init_device() { int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { - set_control_map(i, axis_map[i]); + // Emulate D-Pad buttons if necessary. + if (i > ABS_HAT0Y) { + set_control_map(i, C_none); + + } else if (i == ABS_HAT0X && emulate_dpad) { + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + + } else if (i == ABS_HAT0Y && emulate_dpad) { + _dpad_y_axis = i; + _dpad_up_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + + } else { + set_control_map(i, axis_map[i]); + } // Check the initial value and ranges. struct input_absinfo absinfo; @@ -284,20 +349,6 @@ init_device() { all_values_zero = false; } } - - // Emulate D-Pad buttons if necessary. - if (i == ABS_HAT0X && !have_dpad_buttons) { - _dpad_x_axis = i; - _dpad_left_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); - } - if (i == ABS_HAT0Y && !have_dpad_buttons) { - _dpad_y_axis = i; - _dpad_up_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); - } } } } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 3ee0f05dcb..04dfc4caae 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -170,9 +170,25 @@ set_button_state(int index, bool down) { _buttons.resize(index + 1, ButtonState()); } - _buttons[index]._state = down ? S_down : S_up; + State new_state = down ? S_down : S_up; + if (_buttons[index]._state == new_state) { + return; + } + _buttons[index]._state = new_state; ButtonHandle handle = _buttons[index]._handle; + + if (device_cat.is_spam()) { + device_cat.spam() + << "Changed button " << index; + + if (handle != ButtonHandle::none()) { + device_cat.spam(false) << " (" << handle << ")"; + } + + device_cat.spam(false) << " to " << (down ? "down" : "up") << "\n"; + } + if (handle != ButtonHandle::none()) { _button_events->add_event(ButtonEvent(handle, down ? ButtonEvent::T_down : ButtonEvent::T_up)); } @@ -192,7 +208,7 @@ set_control_state(int index, double state) { _controls.resize(index + 1, AnalogState()); } - if (device_cat.is_spam()) { + if (device_cat.is_spam() && _controls[index]._state != state) { device_cat.spam() << "Changed control " << index; diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 0df79c3c65..e2e62735ff 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -73,6 +73,10 @@ PUBLISHED: DC_flight_stick, DC_steering_wheel, + DC_dance_pad, + + // Head-mounted display. + DC_hmd, }; protected: @@ -101,6 +105,14 @@ PUBLISHED: C_y, C_trigger, C_throttle, + C_rudder, + C_hat_x, + C_hat_y, + + // Steering wheel / pedals + C_wheel, + C_accelerator, + C_brake, }; INLINE string get_name() const; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index ed4e7fa329..f6ab5c7438 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -257,26 +257,50 @@ open_device() { axis = C_right_trigger; break; + case ABS_THROTTLE: + axis = InputDevice::C_throttle; + break; + + case ABS_RUDDER: + axis = InputDevice::C_rudder; + break; + + case ABS_WHEEL: + axis = InputDevice::C_wheel; + break; + + case ABS_GAS: + axis = InputDevice::C_accelerator; + break; + + case ABS_BRAKE: + axis = InputDevice::C_brake; + break; + case ABS_HAT0X: - if (_dpad_left_button == -1) { + if (_dpad_left_button == -1 && _device_class == DC_gamepad) { // Emulate D-Pad. _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + axis = C_none; + } else { + axis = C_hat_x; } - axis = C_none; break; case ABS_HAT0Y: - if (_dpad_up_button == -1) { + if (_dpad_up_button == -1 && _device_class == DC_gamepad) { // Emulate D-Pad. _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + axis = C_none; + } else { + axis = C_hat_y; } - axis = C_none; break; default: diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 8650d1e89b..9fe3778f8c 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -28,11 +28,33 @@ #ifndef XINPUT_CAPS_FFB_SUPPORTED #define XINPUT_CAPS_FFB_SUPPORTED 0x0001 #endif +#ifndef XINPUT_CAPS_NO_NAVIGATION +#define XINPUT_CAPS_NO_NAVIGATION 0x0010 +#endif #ifndef BATTERY_DEVTYPE_GAMEPAD #define BATTERY_DEVTYPE_GAMEPAD 0x00 #endif +#ifndef XINPUT_DEVSUBTYPE_WHEEL +#define XINPUT_DEVSUBTYPE_WHEEL 0x02 +#endif +#ifndef XINPUT_DEVSUBTYPE_ARCADE_STICK +#define XINPUT_DEVSUBTYPE_ARCADE_STICK 0x03 +#endif +#ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK +#define XINPUT_DEVSUBTYPE_FLIGHT_STICK 0x04 +#endif +#ifndef XINPUT_DEVSUBTYPE_DANCE_PAD +#define XINPUT_DEVSUBTYPE_DANCE_PAD 0x05 +#endif +#ifndef XINPUT_DEVSUBTYPE_GUITAR +#define XINPUT_DEVSUBTYPE_GUITAR 0x06 +#endif +#ifndef XINPUT_DEVSUBTYPE_DRUM_KIT +#define XINPUT_DEVSUBTYPE_DRUM_KIT 0x08 +#endif + #ifndef BATTERY_TYPE_DISCONNECTED #define BATTERY_TYPE_DISCONNECTED 0x00 #endif @@ -96,29 +118,6 @@ XInputDevice(DWORD user_index) : _controls.resize(6); _buttons.resize(16); - set_control_map(0, C_left_trigger); - set_control_map(1, C_right_trigger); - set_control_map(2, C_left_x); - set_control_map(3, C_left_y); - set_control_map(4, C_right_x); - set_control_map(5, C_right_y); - - set_button_map(0, GamepadButton::dpad_up()); - set_button_map(1, GamepadButton::dpad_down()); - set_button_map(2, GamepadButton::dpad_left()); - set_button_map(3, GamepadButton::dpad_right()); - set_button_map(4, GamepadButton::start()); - set_button_map(5, GamepadButton::back()); - set_button_map(6, GamepadButton::lstick()); - set_button_map(7, GamepadButton::rstick()); - set_button_map(8, GamepadButton::lshoulder()); - set_button_map(9, GamepadButton::rshoulder()); - set_button_map(10, GamepadButton::guide()); - set_button_map(11, GamepadButton::action_a()); - set_button_map(12, GamepadButton::action_b()); - set_button_map(13, GamepadButton::action_x()); - set_button_map(14, GamepadButton::action_y()); - // Check if the device is connected. If so, initialize it. XINPUT_CAPABILITIES caps; XINPUT_STATE state; @@ -242,19 +241,87 @@ init_xinput() { */ void XInputDevice:: init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { - if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { + // It seems that the Xbox One controller is reported as having a DevType of + // zero, at least when I tested in with XInput 1.3 on Windows 7. + //if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { + + // For subtypes and mappings, see this page: + // https://msdn.microsoft.com/en-us/library/windows/desktop/hh405050.aspx + switch (caps.SubType) { + default: + case XINPUT_DEVSUBTYPE_GAMEPAD: _device_class = DC_gamepad; - } else { - _device_class = DC_unknown; + set_control_map(0, C_left_trigger); + set_control_map(1, C_right_trigger); + set_control_map(2, C_left_x); + set_control_map(3, C_left_y); + set_control_map(4, C_right_x); + set_control_map(5, C_right_y); + break; + + case XINPUT_DEVSUBTYPE_WHEEL: + _device_class = DC_steering_wheel; + set_control_map(0, C_brake); + set_control_map(1, C_accelerator); + set_control_map(2, C_wheel); + set_control_map(3, C_none); + set_control_map(4, C_none); + set_control_map(5, C_none); + break; + + case XINPUT_DEVSUBTYPE_FLIGHT_STICK: + _device_class = DC_flight_stick; + set_control_map(0, C_rudder); + set_control_map(1, C_throttle); + set_control_map(2, C_x); + set_control_map(3, C_y); + set_control_map(4, C_hat_x); + set_control_map(5, C_hat_y); + break; + + case XINPUT_DEVSUBTYPE_DANCE_PAD: + _device_class = DC_dance_pad; + set_control_map(0, C_none); + set_control_map(1, C_none); + set_control_map(2, C_none); + set_control_map(3, C_none); + set_control_map(4, C_none); + set_control_map(5, C_none); + break; } + if (caps.Flags & XINPUT_CAPS_NO_NAVIGATION) { + set_button_map(0, ButtonHandle::none()); + set_button_map(1, ButtonHandle::none()); + set_button_map(2, ButtonHandle::none()); + set_button_map(3, ButtonHandle::none()); + set_button_map(4, ButtonHandle::none()); + set_button_map(5, ButtonHandle::none()); + } else { + set_button_map(0, GamepadButton::dpad_up()); + set_button_map(1, GamepadButton::dpad_down()); + set_button_map(2, GamepadButton::dpad_left()); + set_button_map(3, GamepadButton::dpad_right()); + set_button_map(4, GamepadButton::start()); + set_button_map(5, GamepadButton::back()); + } + set_button_map(6, GamepadButton::lstick()); + set_button_map(7, GamepadButton::rstick()); + set_button_map(8, GamepadButton::lshoulder()); + set_button_map(9, GamepadButton::rshoulder()); + set_button_map(10, GamepadButton::guide()); + set_button_map(11, GamepadButton::action_a()); + set_button_map(12, GamepadButton::action_b()); + set_button_map(13, GamepadButton::action_x()); + set_button_map(14, GamepadButton::action_y()); + if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { _flags |= IDF_has_vibration; } if (get_battery_information != NULL) { XINPUT_BATTERY_INFORMATION batt; - if (get_battery_information(_index, 0, &batt) == ERROR_SUCCESS) { + if (get_battery_information(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) { if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED && batt.BatteryType != BATTERY_TYPE_WIRED) { // This device has a battery. Report the battery level. @@ -276,7 +343,7 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { { // Reformat the serial number into its original hex string form. char sn[10]; - sprintf(sn, "%08X", businfo.InstanceID); + sprintf_s(sn, 10, "%08X", businfo.InstanceID); _serial_number.assign(sn, 8); } @@ -284,7 +351,7 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { // first need to construct the device path. Fortunately, we now have // enough information to do so. char path[32]; - sprintf(path, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID); + sprintf_s(path, 32, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID); DEVINST inst; if (CM_Locate_DevNodeA(&inst, path, 0) != 0) { From c7dbf193f1ffb43c4257e8a9611a4cc4e12a9650 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 28 Jun 2016 17:49:47 +0200 Subject: [PATCH 15/82] Fix Linux crash with Xbox One controller, fix compile warnings --- panda/src/device/evdevInputDevice.cxx | 54 +++++++++++++----------- panda/src/device/linuxJoystickDevice.cxx | 31 ++++++++------ 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 4aa94e023d..bb91144d6a 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -181,7 +181,10 @@ do_set_vibration(double strong, double weak) { play.code = _ff_id; play.value = 1; - write(_fd, &play, sizeof(play)); + if (write(_fd, &play, sizeof(play)) < 0) { + device_cat.warning() + << "Failed to write force-feedback event: " << strerror(errno) << "\n"; + } } } @@ -301,22 +304,22 @@ init_device() { int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { - // Emulate D-Pad buttons if necessary. - if (i > ABS_HAT0Y) { + if (i >= ABS_HAT0X) { set_control_map(i, C_none); - } else if (i == ABS_HAT0X && emulate_dpad) { - _dpad_x_axis = i; - _dpad_left_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); - - } else if (i == ABS_HAT0Y && emulate_dpad) { - _dpad_y_axis = i; - _dpad_up_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + // Emulate D-Pad buttons if necessary. + if (i == ABS_HAT0X && emulate_dpad) { + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } else if (i == ABS_HAT0Y && emulate_dpad) { + _dpad_y_axis = i; + _dpad_up_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } } else { set_control_map(i, axis_map[i]); } @@ -379,27 +382,30 @@ init_device() { sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); FILE *f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - if (buffer[0] != 0) { - _name.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } } fclose(f); } sprintf(path, "/sys/class/input/event%d/device/device/../manufacturer", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - _manufacturer.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + } fclose(f); } sprintf(path, "/sys/class/input/event%d/device/device/../serial", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - _serial_number.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + } fclose(f); } diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index f6ab5c7438..8c3a91cb6b 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -319,40 +319,47 @@ open_device() { sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index); FILE *f = fopen(path, "r"); if (f) { - fscanf(f, "%hx", &_vendor_id); + if (fscanf(f, "%hx", &_vendor_id) < 1) { + _vendor_id = 0; + } fclose(f); } sprintf(path, "/sys/class/input/js%d/device/id/product", _index); f = fopen(path, "r"); if (f) { - fscanf(f, "%hx", &_product_id); + if (fscanf(f, "%hx", &_product_id) < 1) { + _product_id = 0; + } fclose(f); } char buffer[256]; sprintf(path, "/sys/class/input/js%d/device/device/../product", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - if (buffer[0] != 0) { - _name.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } } fclose(f); } sprintf(path, "/sys/class/input/js%d/device/device/../manufacturer", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - _manufacturer.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + } fclose(f); } sprintf(path, "/sys/class/input/js%d/device/device/../serial", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - _serial_number.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + } fclose(f); } From dcdbe4b339baf0134228787ecbb0f6495d1fc186 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 21 Jul 2016 12:44:44 +0200 Subject: [PATCH 16/82] Add handling for new gamepad buttons Added handles to 1, 2, next and previous as available on the wiimote --- panda/src/device/evdevInputDevice.cxx | 12 ++++++++++++ panda/src/device/linuxJoystickDevice.cxx | 16 ++++++++++++++++ panda/src/putil/gamepadButton.cxx | 12 ++++++++++++ panda/src/putil/gamepadButton.h | 6 ++++++ 4 files changed, 46 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index bb91144d6a..0bc4a8d9ff 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -693,6 +693,12 @@ map_button(int code) { case BTN_TR2: return GamepadButton::rtrigger(); + case BTN_1: + return GamepadButton::action_1(); + + case BTN_2: + return GamepadButton::action_2(); + case BTN_SELECT: return GamepadButton::back(); @@ -702,6 +708,12 @@ map_button(int code) { case BTN_MODE: return GamepadButton::guide(); + case KEY_NEXT: + return GamepadButton::next(); + + case KEY_PREVIOUS: + return GamepadButton::previous(); + case BTN_THUMBL: return GamepadButton::lstick(); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 8c3a91cb6b..b6b622412b 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -163,6 +163,14 @@ open_device() { handle = GamepadButton::rtrigger(); break; + case BTN_1: + handle = GamepadButton::action_1(); + break; + + case BTN_2: + handle = GamepadButton::action_2(); + break; + case BTN_SELECT: handle = GamepadButton::back(); break; @@ -175,6 +183,14 @@ open_device() { handle = GamepadButton::guide(); break; + case KEY_NEXT: + handle = GamepadButton::next(); + break; + + case KEY_PREVIOUS: + handle = GamepadButton::previous(); + break; + case BTN_THUMBL: handle = GamepadButton::lstick(); break; diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index fd6fa1b396..ba7fe41b5a 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -34,6 +34,9 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(back) DEFINE_GAMEPAD_BUTTON_HANDLE(guide) DEFINE_GAMEPAD_BUTTON_HANDLE(start) +DEFINE_GAMEPAD_BUTTON_HANDLE(next) +DEFINE_GAMEPAD_BUTTON_HANDLE(previous) + DEFINE_GAMEPAD_BUTTON_HANDLE(action_a) DEFINE_GAMEPAD_BUTTON_HANDLE(action_b) DEFINE_GAMEPAD_BUTTON_HANDLE(action_c) @@ -41,6 +44,9 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(action_x) DEFINE_GAMEPAD_BUTTON_HANDLE(action_y) DEFINE_GAMEPAD_BUTTON_HANDLE(action_z) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_1) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_2) + /** * This is intended to be called only once, by the static initialization * performed in config_util.cxx. @@ -63,10 +69,16 @@ init_gamepad_buttons() { ButtonRegistry::ptr()->register_button(_guide, "guide"); ButtonRegistry::ptr()->register_button(_start, "start"); + ButtonRegistry::ptr()->register_button(_next, "next"); + ButtonRegistry::ptr()->register_button(_previous, "previous"); + ButtonRegistry::ptr()->register_button(_action_a, "action_a"); ButtonRegistry::ptr()->register_button(_action_b, "action_b"); ButtonRegistry::ptr()->register_button(_action_c, "action_c"); ButtonRegistry::ptr()->register_button(_action_x, "action_x"); ButtonRegistry::ptr()->register_button(_action_y, "action_y"); ButtonRegistry::ptr()->register_button(_action_z, "action_z"); + + ButtonRegistry::ptr()->register_button(_action_1, "action_1"); + ButtonRegistry::ptr()->register_button(_action_2, "action_2"); } diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index b41f494b71..9a8597a6b9 100644 --- a/panda/src/putil/gamepadButton.h +++ b/panda/src/putil/gamepadButton.h @@ -40,6 +40,9 @@ PUBLISHED: static ButtonHandle guide(); static ButtonHandle start(); + static ButtonHandle next(); + static ButtonHandle previous(); + static ButtonHandle action_a(); static ButtonHandle action_b(); static ButtonHandle action_c(); @@ -47,6 +50,9 @@ PUBLISHED: static ButtonHandle action_y(); static ButtonHandle action_z(); + static ButtonHandle action_1(); + static ButtonHandle action_2(); + public: static void init_gamepad_buttons(); }; From a6110a606eb56ebc5149a91acea23a2e10909cf8 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 21 Jul 2016 12:46:20 +0200 Subject: [PATCH 17/82] renamed gamepad to device in inputDeviceManager renamed as not every device is a gamepad --- panda/src/device/inputDeviceManager.cxx | 29 ++++++++++++++++++++----- panda/src/device/inputDeviceManager.h | 7 +++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index e4be920583..edcc2a7903 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -246,23 +246,40 @@ consider_add_js_device(int js_index) { #endif /** - * Description: Returns all currently connected gamepad devices. + * Description: Returns all currently connected devices. */ InputDeviceSet InputDeviceManager:: -get_gamepads() const { - InputDeviceSet gamepads; +get_devices() const { + InputDeviceSet devices; LightMutexHolder holder(_lock); for (size_t i = 0; i < _connected_devices.size(); ++i) { InputDevice *device = _connected_devices[i]; - if (device->get_device_class() == InputDevice::DC_gamepad) { - gamepads.add_device(device); + devices.add_device(device); + } + + return devices; +} + +/** + * Description: Returns all currently connected devices of the given device class. + */ +InputDeviceSet InputDeviceManager:: +get_devices(InputDevice::DeviceClass device_class) const { + InputDeviceSet devices; + LightMutexHolder holder(_lock); + + for (size_t i = 0; i < _connected_devices.size(); ++i) { + InputDevice *device = _connected_devices[i]; + if (device->get_device_class() == device_class) { + devices.add_device(device); } } - return gamepads; + return devices; } + /** * Called when a new device has been discovered. This may also be used to * register virtual devices. diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 1bb3c353cd..9459f65c0d 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -38,7 +38,8 @@ private: #endif public: - InputDeviceSet get_gamepads() const; + InputDeviceSet get_devices() const; + InputDeviceSet get_devices(InputDevice::DeviceClass device_class) const; PUBLISHED: void add_device(InputDevice *device); @@ -48,8 +49,8 @@ PUBLISHED: INLINE static InputDeviceManager *get_global_ptr(); - // The set of all currently connected gamepad devices. - MAKE_PROPERTY(gamepads, get_gamepads); + // The set of all currently connected devices. + MAKE_PROPERTY(devices, get_devices); private: LightMutex _lock; From ef7cc925c4b3178fd59976ff8ec1a3db93963af6 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 21 Jul 2016 23:09:09 +0200 Subject: [PATCH 18/82] Add data graph node for devices Add the new inputDeviceNode class to be used in the data graph Add config variable for low batery level --- panda/src/device/config_device.cxx | 3 + panda/src/device/config_device.h | 2 + panda/src/device/inputDeviceNode.cxx | 71 ++++++++++++++++++++++++ panda/src/device/inputDeviceNode.h | 66 ++++++++++++++++++++++ panda/src/device/p3device_composite1.cxx | 1 + 5 files changed, 143 insertions(+) create mode 100644 panda/src/device/inputDeviceNode.cxx create mode 100644 panda/src/device/inputDeviceNode.h diff --git a/panda/src/device/config_device.cxx b/panda/src/device/config_device.cxx index fdb9ab8c52..dc0c458f8c 100644 --- a/panda/src/device/config_device.cxx +++ b/panda/src/device/config_device.cxx @@ -36,6 +36,9 @@ NotifyCategoryDef(device, ""); ConfigVariableBool asynchronous_clients ("asynchronous-clients", true); +ConfigVariableInt low_battery_level +("low-battery-level", 15); + ConfigureFn(config_device) { init_libdevice(); } diff --git a/panda/src/device/config_device.h b/panda/src/device/config_device.h index 8a7b6542a7..a0e6aa8341 100644 --- a/panda/src/device/config_device.h +++ b/panda/src/device/config_device.h @@ -17,10 +17,12 @@ #include "pandabase.h" #include "notifyCategoryProxy.h" #include "configVariableBool.h" +#include "configVariableInt.h" NotifyCategoryDecl(device, EXPCL_PANDA_DEVICE, EXPTP_PANDA_DEVICE); extern ConfigVariableBool asynchronous_clients; +extern ConfigVariableInt low_battery_level; extern EXPCL_PANDA_DEVICE void init_libdevice(); diff --git a/panda/src/device/inputDeviceNode.cxx b/panda/src/device/inputDeviceNode.cxx new file mode 100644 index 0000000000..662b2dbac8 --- /dev/null +++ b/panda/src/device/inputDeviceNode.cxx @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file InputDeviceNode.cxx + * @author fireclaw + * @date 2016-07-14 + */ + +#include "config_device.h" +#include "inputDeviceNode.h" +#include "dataNodeTransmit.h" +#include "inputDeviceManager.h" + +TypeHandle InputDeviceNode::_type_handle; + +InputDeviceNode:: +InputDeviceNode(InputDevice *device, const string &name) : + DataNode(name), + _device(device) +{ + _button_events_output = define_output("button_events", ButtonEventList::get_class_type()); + _low_battery_event_output = define_output("low_battery_level_event", EventStoreInt::get_class_type()); +} + +/** + * Redirects the class to get the data from a different device. + */ +void InputDeviceNode:: +set_device(InputDevice *device) { + _device = device; +} + +/** + * Returns the associated device. + */ +PT(InputDevice) InputDeviceNode:: +get_device() const { + return _device; +} + +/** + * The virtual implementation of transmit_data(). This function receives an + * array of input parameters and should generate an array of output + * parameters. The input parameters may be accessed with the index numbers + * returned by the define_input() calls that were made earlier (presumably in + * the constructor); likewise, the output parameters should be set with the + * index numbers returned by the define_output() calls. + */ +void InputDeviceNode:: +do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, + DataNodeTransmit &output) { + // get all button events of the device and forward them to the data graph + if (_device->has_button_event()) { + PT(ButtonEventList) bel = _device->get_button_events(); + output.set_data(_button_events_output, EventParameter(bel)); + } + + // calculate the battery level in percent and set a warning if the level is to low + if (_device->has_battery()) { + short bl = _device->get_battery_level(); + short bl_percent = bl / (_device->get_max_battery_level() / (short)100); + if (bl_percent <= low_battery_level) { + output.set_data(_low_battery_event_output, EventParameter(1)); + } + } +} diff --git a/panda/src/device/inputDeviceNode.h b/panda/src/device/inputDeviceNode.h new file mode 100644 index 0000000000..a8e6baef57 --- /dev/null +++ b/panda/src/device/inputDeviceNode.h @@ -0,0 +1,66 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file InputDeviceNode.h + * @author fireclaw + * @date 2016-07-14 + */ + +#ifndef INPUTDEVICENODE_H +#define INPUTDEVICENODE_H + +#include "pandabase.h" + +#include "dataNode.h" +#include "inputDeviceManager.h" +#include "linmath_events.h" + +/** + * Reads the controler data sent from the InputDeviceManager, and + * transmits it down the data graph. + * + * + */ +class EXPCL_PANDA_DEVICE InputDeviceNode : public DataNode { +PUBLISHED: + InputDeviceNode(InputDevice *device, const string &name); + void set_device(InputDevice *device); + PT(InputDevice) get_device() const; + +protected: + // Inherited from DataNode + virtual void do_transmit_data(DataGraphTraverser *trav, + const DataNodeTransmit &input, + DataNodeTransmit &output); + +private: + // outputs + int _button_events_output; + int _low_battery_event_output; + + PT(InputDevice) _device; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + DataNode::init_type(); + register_type(_type_handle, "InputDeviceNode", + DataNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif // INPUTDEVICENODE_H diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 2e541a6f47..90c0b54133 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -10,3 +10,4 @@ #include "inputDeviceManager.cxx" #include "inputDeviceSet.cxx" #include "linuxJoystickDevice.cxx" +#include "inputDeviceNode.cxx" From 2e8338c681a4642fba04f35b60ba9f1bd73cd660 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 21 Jul 2016 23:12:07 +0200 Subject: [PATCH 19/82] Add the new inputDeviceNode to the data graph create node and add to data graph for any connected device add function to add and remove devices and call them on dis-/connection add poll call for all devices in the __dataLoop task --- direct/src/showbase/ShowBase.py | 80 +++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 0916e83f7d..2900c7aeae 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -177,6 +177,7 @@ class ShowBase(DirectObject.DirectObject): self.trackball = None self.texmem = None self.showVertices = None + self.deviceButtonThrowers = [] ## This is a NodePath pointing to the Camera object set up for the 3D scene. ## This is usually a child of self.camera. @@ -301,6 +302,7 @@ class ShowBase(DirectObject.DirectObject): ## The global job manager, as imported from JobManagerGlobal. self.jobMgr = jobMgr + ## Particle manager self.particleMgr = None self.particleMgrEnabled = 0 @@ -313,6 +315,12 @@ class ShowBase(DirectObject.DirectObject): ## This is the global input device manager, which keeps track of ## connected input devices. self.devices = InputDeviceManager.getGlobalPtr() + # add existing devices to the data graph + for device in self.devices.devices: + self.connectDevice(device) + # Checks for device connection and disconnection + self.accept('connect-device', self.connectDevice) + self.accept('disconnect-device', self.disconnectDevice) self.createStats() @@ -1664,6 +1672,74 @@ class ShowBase(DirectObject.DirectObject): return self.mouseWatcherNode.getModifierButtons().isDown( KeyboardButton.meta()) + def connectDevice(self, device): + """ + This function will get called each time a new device got + connected and will add that new device to the data graph. + + Each device class will get a specific prefix for thrown events. Those + are currently as follow + + gamepad + flight_stick + steering_wheel + dance_pad + mouse + keyboard + unclassified_device + + In addition, the index of that device will appended to the prefix, + so for example if you hit the A button of the first connected gamepad + you will get an event like "gamepad0-action_a" the second gamepad will + then be catchable via "gamepad1-button_event" and so on. + Note, each device class will have a separate 0 based index, this way + you can have a gamepad0 as well as a steering_wheel0 and flight_stick0. + + All newly created button throwers will be stored in + the deviceButtonThrowers lsit + """ + idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.getName())) + prefix = "unclassified_device" + if device.getDeviceClass() == InputDevice.DC_gamepad: + prefix = "gamepad" + elif device.getDeviceClass() == InputDevice.DC_flight_stick: + prefix = "flight_stick" + elif device.getDeviceClass() == InputDevice.DC_steering_wheel: + prefix = "steering_wheel" + elif device.getDeviceClass() == InputDevice.DC_dance_pad: + prefix = "dance_pad" + elif device.getDeviceClass() == InputDevice.DC_mouse: + prefix = "mouse" + elif device.getDeviceClass() == InputDevice.DC_keyboard: + prefix = "keyboard" + + id = 0 + for dev in self.devices.devices: + if dev == device: + break + elif dev.getDeviceClass() == device.getDeviceClass(): + id += 1 + print "" + print "MAPPED", device, "AS", prefix, "SET CLASS", device.getDeviceClass() + print "" + bt = idn.attachNewNode(ButtonThrower(prefix)) + bt.node().setPrefix("{}{}-".format(prefix, id)) + self.deviceButtonThrowers.append(bt) + + def disconnectDevice(self, device): + """ + This function will get called each time a new device got + connected. It is then used to clean up the given device from the + data graph. + """ + print device.getName() + idn = self.dataRoot.find("**/{}".format(device.getName)) + for bt in self.deviceButtonThrowers.list(): + if bt.getName() == idn.getName(): + self.deviceButtonThrowers.remove(bt) + break + self.dataRoot.removeNode(idn) + def addAngularIntegrator(self): if not self.physicsMgrAngular: physics = importlib.import_module('panda3d.physics') @@ -1856,6 +1932,10 @@ class ShowBase(DirectObject.DirectObject): # Check if there were newly connected devices. self.devices.update() + # Poll all connected devices. + for device in self.devices.devices: + device.poll() + # traverse the data graph. This reads all the control # inputs (from the mouse and keyboard, for instance) and also # directly acts upon them (for instance, to move the avatar). From 271997b46219b12a8d707599d59bb7750e79e748 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Fri, 22 Jul 2016 21:25:27 +0200 Subject: [PATCH 20/82] Moved get_devices functions into PUBLISHED section --- panda/src/device/inputDeviceManager.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 9459f65c0d..9596a841d2 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -37,11 +37,10 @@ private: InputDevice *consider_add_js_device(int index); #endif -public: +PUBLISHED: InputDeviceSet get_devices() const; InputDeviceSet get_devices(InputDevice::DeviceClass device_class) const; -PUBLISHED: void add_device(InputDevice *device); void remove_device(InputDevice *device); From 29896a39a478dc256f93e3c8de0c0ae0cf2a2d8b Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Fri, 22 Jul 2016 21:33:41 +0200 Subject: [PATCH 21/82] cleanup and fixes fixed event prefix ID handling fixed removing of nodes of disconnected devices removed debug prints added more comments --- direct/src/showbase/ShowBase.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 2900c7aeae..b1e84d1aae 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -1713,17 +1713,20 @@ class ShowBase(DirectObject.DirectObject): elif device.getDeviceClass() == InputDevice.DC_keyboard: prefix = "keyboard" + currentPrefixes = [] + for np in self.dataRoot.findAllMatches("**/{}".format(prefix)): + bt = np.node() + currentPrefixes.append(bt.getPrefix()) + id = 0 - for dev in self.devices.devices: - if dev == device: - break - elif dev.getDeviceClass() == device.getDeviceClass(): - id += 1 - print "" - print "MAPPED", device, "AS", prefix, "SET CLASS", device.getDeviceClass() - print "" + # Find the next free ID for the newly connected device + while "{}{}-".format(prefix, id) in currentPrefixes: + id+=1 + # Setup the button thrower for that device and register it's event prefix bt = idn.attachNewNode(ButtonThrower(prefix)) + self.notify.debug("Registred event prefix {}{}-".format(prefix, id)) bt.node().setPrefix("{}{}-".format(prefix, id)) + # append the new button thrower to the list of device button throwers self.deviceButtonThrowers.append(bt) def disconnectDevice(self, device): @@ -1732,13 +1735,13 @@ class ShowBase(DirectObject.DirectObject): connected. It is then used to clean up the given device from the data graph. """ - print device.getName() - idn = self.dataRoot.find("**/{}".format(device.getName)) - for bt in self.deviceButtonThrowers.list(): + self.notify.debug("Disconnect device {}".format(device.getName())) + idn = self.dataRoot.find("**/{}".format(device.getName())) + for bt in list(self.deviceButtonThrowers): if bt.getName() == idn.getName(): self.deviceButtonThrowers.remove(bt) break - self.dataRoot.removeNode(idn) + idn.removeNode() def addAngularIntegrator(self): if not self.physicsMgrAngular: From a98cc38c90a28df553201b7e1f8947702b348ad4 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Fri, 22 Jul 2016 21:34:10 +0200 Subject: [PATCH 22/82] Added gamepad device sample script --- samples/gamepad/main.py | 116 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 samples/gamepad/main.py diff --git a/samples/gamepad/main.py b/samples/gamepad/main.py new file mode 100644 index 0000000000..189c4e0f16 --- /dev/null +++ b/samples/gamepad/main.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +''' +Demonstrate usage of gamepads and other input devices + +In this sample you can use a gamepad type device to control the camera and +show some messages on screen. Using the left stick on the controler will +move the camera where the right stick will rotate the camera. +''' + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 +from direct.gui.OnscreenText import OnscreenText + +loadPrcFileData("", "notify-level-device debug") + +class App(ShowBase): + def __init__(self): + ShowBase.__init__(self) + # print all events sent through the messenger + self.messenger.toggleVerbose() + + self.lblWarning = OnscreenText( + text = "No devices found", + fg=(1,0,0,1), + scale = .25) + self.lblWarning.hide() + + self.lblAction = OnscreenText( + text = "Action", + fg=(1,1,1,1), + scale = .15) + self.lblAction.hide() + + self.checkDevices() + + # Accept device dis-/connection events + # NOTE: catching the events here will overwrite the accept in showbase, hence + # we need to forward the event in the functions we set here! + self.accept("connect-device", self.connect) + self.accept("disconnect-device", self.disconnect) + + self.accept("escape", exit) + self.accept("gamepad0-start", exit) + self.accept("flight_stick0-start", exit) + + # Accept button events of the first connected gamepad + self.accept("gamepad0-action_a", self.doAction, extraArgs=[True, "Action"]) + self.accept("gamepad0-action_a-up", self.doAction, extraArgs=[False, "Release"]) + self.accept("gamepad0-action_b", self.doAction, extraArgs=[True, "Action 2"]) + self.accept("gamepad0-action_b-up", self.doAction, extraArgs=[False, "Release"]) + + self.environment = loader.loadModel("environment") + self.environment.reparentTo(render) + + # disable pandas default mouse-camera controls so we can handle the camera + # movements by ourself + self.disableMouse() + + self.taskMgr.add(self.moveTask, "movement update task") + + def connect(self, device): + # we need to forward the event to the connectDevice function of showbase + self.connectDevice(device) + # Now we can check for ourself + self.checkDevices() + + def disconnect(self, device): + # we need to forward the event to the disconnectDevice function of showbase + self.disconnectDevice(device) + # Now we can check for ourself + self.checkDevices() + + def checkDevices(self): + # check if we have gamepad devices connected + if self.devices.get_devices(InputDevice.DC_gamepad): + # we have at least one gamepad device + self.lblWarning.hide() + else: + # no devices connected + self.lblWarning.show() + + def doAction(self, showText, text): + if showText and self.lblAction.isHidden(): + self.lblAction.show() + else: + self.lblAction.hide() + + def moveTask(self, task): + dt = globalClock.getDt() + movementVec = Vec3() + + gamepads = base.devices.getDevices(InputDevice.DC_gamepad) + if len(gamepads) == 0: + # savety check + return task.cont + + # we will use the first found gamepad + for i in range(gamepads[0].getNumControls()): + if gamepads[0].getControlMap(i) == InputDevice.C_left_x: + movementVec.setX(gamepads[0].getControlState(i)) + elif gamepads[0].getControlMap(i) == InputDevice.C_left_y: + movementVec.setY(gamepads[0].getControlState(i)) + + if gamepads[0].getControlMap(i) == InputDevice.C_right_x: + base.camera.setH(base.camera, 100 * dt * (gamepads[0].getControlState(i))) + elif gamepads[0].getControlMap(i) == InputDevice.C_right_y: + base.camera.setP(base.camera, 100 * dt * (gamepads[0].getControlState(i))) + + # calculate movement + base.camera.setX(base.camera, 100 * dt * movementVec.getX()) + base.camera.setY(base.camera, 100 * dt * movementVec.getY()) + + return task.cont + +app = App() +app.run() From 670efb9e646b0cd17404b6d14fa754ce5d715f2d Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 3 Aug 2016 20:20:01 +0200 Subject: [PATCH 23/82] Extended device class heuristics Extended and partly restructured the way how the device class gets set Added DC_COUNT to the DeviceClass enum to simplify loop checks --- panda/src/device/evdevInputDevice.cxx | 137 ++++++++++++++++++++------ panda/src/device/inputDevice.h | 3 + 2 files changed, 108 insertions(+), 32 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 0bc4a8d9ff..bdc82df218 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -227,6 +227,7 @@ init_device() { } _name.assign(name); + //cerr << "##### Now initializing device " << name << "\n"; struct input_id id; if (ioctl(_fd, EVIOCGID, &id) >= 0) { @@ -237,12 +238,111 @@ init_device() { bool all_values_zero = true; bool emulate_dpad = true; + bool has_keys = false; + bool has_axes = false; + + uint8_t keys[(KEY_CNT + 7) >> 3]; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. - uint8_t keys[(KEY_CNT + 7) >> 3]; memset(keys, 0, sizeof(keys)); ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys); + has_keys = true; + if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) { + _flags |= IDF_has_keyboard; + } + } + + int num_bits = 0; + uint8_t axes[(ABS_CNT + 7) >> 3]; + if (test_bit(EV_ABS, evtypes)) { + // Check which axes are on the device. + memset(axes, 0, sizeof(axes)); + num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; + has_axes = true; + } + + + // Try to detect which type of device we have here + int device_scores[DC_COUNT]; + memset(device_scores, 0, sizeof(device_scores)); + + // Test for specific keys + if (test_bit(BTN_GAMEPAD, keys)) { + device_scores[DC_gamepad] += 5; + device_scores[DC_steering_wheel] += 5; + device_scores[DC_flight_stick] += 5; + } + + if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) { + device_scores[DC_steering_wheel] += 10; + } + if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) { + device_scores[DC_steering_wheel] += 10; + } + if (test_bit(BTN_JOYSTICK, keys)) { + device_scores[DC_flight_stick] += 10; + } + if (test_bit(BTN_MOUSE, keys)) { + device_scores[DC_mouse] += 20; + } + uint8_t unknown_keys[] = {KEY_POWER}; + for (int i = 0; i < 1; i++) { + if (test_bit(unknown_keys[i], keys)) { + if (unknown_keys[i] == KEY_POWER) { + } + device_scores[DC_unknown] += 20; + } + } + if (_flags & IDF_has_keyboard) { + device_scores[DC_keyboard] += 20; + } + + // Test for specific name tags + string lowercase_name = _name; + for(int x=0; x<_name.length(); x++) { + lowercase_name[x]=tolower(lowercase_name[x]); + } + if (lowercase_name.find("gamepad") != string::npos) { + device_scores[DC_gamepad] += 10; + } + if (lowercase_name.find("wheel") != string::npos) { + device_scores[DC_steering_wheel] += 10; + } + if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) { + device_scores[DC_mouse] += 10; + } + if (lowercase_name.find("keyboard") != string::npos) { + device_scores[DC_keyboard] += 10; + } + // List of lowercase names that occur in unknown devices + string unknown_names[] = {"video bus", "power button", "sleep button"}; + for(int i = 0; i < 3; i++) { + if (lowercase_name.find(unknown_names[i]) != string::npos) { + device_scores[DC_unknown] += 20; + } + } + + // Check which device type got the most points + bool highscore_found = false; + size_t highest_score = 0; + while (!highscore_found) { + highscore_found = true; + for (size_t i = 0; i < DC_COUNT; i++) { + if (device_scores[i] > highest_score) { + highest_score = device_scores[i]; + _device_class = (DeviceClass)i; + highscore_found = false; + } + } + } + //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; + + if (_device_class != DC_gamepad) { + emulate_dpad = false; + } + + if (has_keys) { // Also check whether the buttons are currently pressed. uint8_t states[(KEY_CNT + 7) >> 3]; memset(states, 0, sizeof(states)); @@ -252,6 +352,7 @@ init_device() { for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { set_button_map(bi, map_button(i)); + //cerr << "Button " << bi << " is mapped by the driver to " << i << "\n"; if (test_bit(i, states)) { _buttons[bi]._state = S_down; all_values_zero = false; @@ -264,44 +365,15 @@ init_device() { ++bi; } } - - if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) { - _flags |= IDF_has_keyboard; - } - - // Check device type. - if (test_bit(BTN_GAMEPAD, keys)) { - _device_class = DC_gamepad; - - } else if (test_bit(BTN_JOYSTICK, keys)) { - _device_class = DC_flight_stick; - - } else if (test_bit(BTN_MOUSE, keys)) { - _device_class = DC_mouse; - - } else if (test_bit(BTN_WHEEL, keys)) { - _device_class = DC_steering_wheel; - - } else if (_flags & IDF_has_keyboard) { - _device_class = DC_keyboard; - } } - if (_device_class != DC_gamepad) { - emulate_dpad = false; - } - - if (test_bit(EV_ABS, evtypes)) { - // Check which axes are on the device. - uint8_t axes[(ABS_CNT + 7) >> 3]; - memset(axes, 0, sizeof(axes)); - + if (has_axes) { AxisRange range; range._scale = 1.0; range._bias = 0.0; _axis_ranges.resize(ABS_CNT, range); - int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; + for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { if (i >= ABS_HAT0X) { @@ -322,6 +394,7 @@ init_device() { } } else { set_control_map(i, axis_map[i]); + //cerr << "Axis " << axis_map[i] << " is mapped by the driver to " << i << "\n"; } // Check the initial value and ranges. diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index e2e62735ff..3d90bc60dd 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -77,6 +77,9 @@ PUBLISHED: // Head-mounted display. DC_hmd, + + // Count of this enum, used for loops + DC_COUNT, }; protected: From cf63a6cccb65ddff0c7e985a4ba41a5cbc34cfcb Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 3 Aug 2016 20:21:08 +0200 Subject: [PATCH 24/82] Added another example showing usage of wheel devices --- samples/gamepad/steeringWheel.py | 139 +++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 samples/gamepad/steeringWheel.py diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py new file mode 100644 index 0000000000..6353f24188 --- /dev/null +++ b/samples/gamepad/steeringWheel.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +''' +Demonstrate usage of steering wheels + +In this sample you can use a wheel type device to control the camera and +show some messages on screen. You can acclerate forward using the +accleration pedal and slow down using the break pedal. +''' + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 +from direct.gui.OnscreenText import OnscreenText + +loadPrcFileData("", "notify-level-device debug") + +class App(ShowBase): + def __init__(self): + ShowBase.__init__(self) + # print all events sent through the messenger + self.messenger.toggleVerbose() + + self.lblWarning = OnscreenText( + text = "No devices found", + fg=(1,0,0,1), + scale = .25) + self.lblWarning.hide() + + self.lblAction = OnscreenText( + text = "Action", + fg=(1,1,1,1), + scale = .15) + self.lblAction.hide() + + self.checkDevices() + + self.currentMoveSpeed = 0.0 + self.maxAccleration = 28.0 + self.deaccleration = 10.0 + self.deaclerationBreak = 37.0 + self.maxSpeed = 80.0 + + # Accept device dis-/connection events + # NOTE: catching the events here will overwrite the accept in showbase, hence + # we need to forward the event in the functions we set here! + self.accept("connect-device", self.connect) + self.accept("disconnect-device", self.disconnect) + + self.accept("escape", exit) + self.accept("flight_stick0-start", exit) + + # Accept button events of the first connected steering wheel + self.accept("steering_wheel0-action_a", self.doAction, extraArgs=[True, "Action"]) + self.accept("steering_wheel0-action_a-up", self.doAction, extraArgs=[False, "Release"]) + + self.environment = loader.loadModel("environment") + self.environment.reparentTo(render) + + self.wheelCenter = 0 + wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) + if len(wheels) > 0: + for i in range(wheels[0].getNumControls()): + if wheels[0].getControlMap(i) == InputDevice.C_wheel: + self.wheelCenter = wheels[0].getControlState(i) + + # disable pandas default mouse-camera controls so we can handle the camera + # movements by ourself + self.disableMouse() + base.camera.setZ(2) + + self.taskMgr.add(self.moveTask, "movement update task") + + def connect(self, device): + # we need to forward the event to the connectDevice function of showbase + self.connectDevice(device) + # Now we can check for ourself + self.checkDevices() + + def disconnect(self, device): + # we need to forward the event to the disconnectDevice function of showbase + self.disconnectDevice(device) + # Now we can check for ourself + self.checkDevices() + + def checkDevices(self): + # check if we have wheel devices connected + if self.devices.get_devices(InputDevice.DC_steering_wheel): + # we have at least one steering wheel device + self.lblWarning.hide() + else: + # no devices connected + self.lblWarning.show() + + def doAction(self, showText, text): + if showText and self.lblAction.isHidden(): + self.lblAction.show() + else: + self.lblAction.hide() + + def moveTask(self, task): + dt = globalClock.getDt() + movementVec = Vec3() + + wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) + if len(wheels) == 0: + # savety check + return task.cont + + if self.currentMoveSpeed > 0: + self.currentMoveSpeed -= dt * self.deaccleration + if self.currentMoveSpeed < 0: + self.currentMoveSpeed = 0 + + # we will use the first found wheel + for i in range(wheels[0].getNumControls()): + + if wheels[0].getControlMap(i) == InputDevice.C_accelerator: + accleration = wheels[0].getControlState(i) * self.maxAccleration + self.currentMoveSpeed += dt * accleration + + if self.currentMoveSpeed > wheels[0].getControlState(i) * self.maxSpeed: + self.currentMoveSpeed -= dt * self.deaccleration + elif wheels[0].getControlMap(i) == InputDevice.C_brake: + deacleration = wheels[0].getControlState(i) * self.deaclerationBreak + self.currentMoveSpeed -= dt * deacleration + + elif self.currentMoveSpeed < 0: + self.currentMoveSpeed = 0 + + if wheels[0].getControlMap(i) == InputDevice.C_wheel: + rotation = self.wheelCenter - wheels[0].getControlState(i) + base.camera.setH(base.camera, 100 * dt * rotation) + + # calculate movement + base.camera.setY(base.camera, dt * self.currentMoveSpeed) + + return task.cont + +app = App() +app.run() From e1661111c29dc55cf55136c2042adaca783dcd98 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 4 Aug 2016 22:52:01 +0200 Subject: [PATCH 25/82] Exposed AnalogState and ButtonState Published Analog- and ButtonState to be accessible from python Added functions to find Controls and buttons to simplify access Refactored highscore search for setting the device class --- panda/src/device/evdevInputDevice.cxx | 21 +++--- panda/src/device/inputDevice.I | 92 ++++++++++++++++++++---- panda/src/device/inputDevice.cxx | 32 ++++----- panda/src/device/inputDevice.h | 23 ++++-- panda/src/device/linuxJoystickDevice.cxx | 8 +-- 5 files changed, 123 insertions(+), 53 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index bdc82df218..573042c5d2 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -324,16 +324,11 @@ init_device() { } // Check which device type got the most points - bool highscore_found = false; size_t highest_score = 0; - while (!highscore_found) { - highscore_found = true; - for (size_t i = 0; i < DC_COUNT; i++) { - if (device_scores[i] > highest_score) { - highest_score = device_scores[i]; - _device_class = (DeviceClass)i; - highscore_found = false; - } + for (size_t i = 0; i < DC_COUNT; i++) { + if (device_scores[i] > highest_score) { + highest_score = device_scores[i]; + _device_class = (DeviceClass)i; } } //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; @@ -354,12 +349,12 @@ init_device() { set_button_map(bi, map_button(i)); //cerr << "Button " << bi << " is mapped by the driver to " << i << "\n"; if (test_bit(i, states)) { - _buttons[bi]._state = S_down; + _buttons[bi].state = S_down; all_values_zero = false; } else { - _buttons[bi]._state = S_up; + _buttons[bi].state = S_up; } - if (_buttons[bi]._handle == GamepadButton::dpad_left()) { + if (_buttons[bi].handle == GamepadButton::dpad_left()) { emulate_dpad = false; } ++bi; @@ -419,7 +414,7 @@ init_device() { _axis_ranges[i]._scale = factor; _axis_ranges[i]._bias = bias; - _controls[i]._state = fma(absinfo.value, factor, bias); + _controls[i].state = fma(absinfo.value, factor, bias); if (absinfo.value != 0) { all_values_zero = false; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index e63930984b..ab30e9eb49 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -197,7 +197,7 @@ set_button_map(int index, ButtonHandle button) { _buttons.resize(index + 1, ButtonState()); } - _buttons[index]._handle = button; + _buttons[index].handle = button; } /** @@ -208,7 +208,7 @@ set_button_map(int index, ButtonHandle button) { INLINE ButtonHandle InputDevice:: get_button_map(int index) const { if (index >= 0 && index < (int)_buttons.size()) { - return _buttons[index]._handle; + return _buttons[index].handle; } else { return ButtonHandle::none(); } @@ -221,7 +221,7 @@ get_button_map(int index) const { INLINE bool InputDevice:: get_button_state(int index) const { if (index >= 0 && index < (int)_buttons.size()) { - return (_buttons[index]._state == S_down); + return (_buttons[index].state == S_down); } else { return false; } @@ -234,12 +234,43 @@ get_button_state(int index) const { INLINE bool InputDevice:: is_button_known(int index) const { if (index >= 0 && index < (int)_buttons.size()) { - return _buttons[index]._state != S_unknown; + return _buttons[index].state != S_unknown; } else { return false; } } +/** + * Returns the ButtonState that is set at the given index, or throw an assection + * if the index was not found in the list. + */ +INLINE InputDevice::ButtonState InputDevice:: +get_button(size_t index) const { + if (index >= 0 && index < (int)_buttons.size()) { + return _buttons[index]; + } else { + /*device_cat.error() + << "Index " << index<< " was not found in the controls list\n";*/ + nassertr(false, ButtonState()); + } +} + +/** + * Returns the first ButtonState found with the given axis, or throw an assection + * if the button handle was not found in the list. + */ +INLINE InputDevice::ButtonState InputDevice:: +find_button(ButtonHandle handle) const { + for (int i; i < (int)_buttons.size(); i++) { + if (_buttons[i].handle == handle) { + return _buttons[i]; + } + } + /*device_cat.error() + << "Axis " << axis << " was not found in the controls list\n";*/ + nassertr(false, ButtonState()); +} + /** * Returns the number of analog controls known to the InputDevice. This number * may change as more controls are discovered. @@ -264,7 +295,7 @@ set_control_map(int index, ControlAxis axis) { _controls.resize(index + 1, AnalogState()); } - _controls[index]._axis = axis; + _controls[index].axis = axis; } /** @@ -275,7 +306,7 @@ set_control_map(int index, ControlAxis axis) { INLINE InputDevice::ControlAxis InputDevice:: get_control_map(int index) const { if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._axis; + return _controls[index].axis; } else { return C_none; } @@ -289,12 +320,43 @@ get_control_map(int index) const { INLINE double InputDevice:: get_control_state(int index) const { if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._state; + return _controls[index].state; } else { return 0.0; } } +/** + * Returns the AnalogAxis that is set at the given index, or throw an assection + * if the index was not found in the list. + */ +INLINE InputDevice::AnalogState InputDevice:: +get_control(size_t index) const { + if (index >= 0 && index < (int)_controls.size()) { + return _controls[index]; + } else { + /*device_cat.error() + << "Index " << index<< " was not found in the controls list\n";*/ + nassertr(false, AnalogState()); + } +} + +/** + * Returns the first AnalogAxis found with the given axis, or throw an assection + * if the axis was not found in the list. + */ +INLINE InputDevice::AnalogState InputDevice:: +find_control(ControlAxis axis) const { + for (int i; i < (int)_controls.size(); i++) { + if (_controls[i].axis == axis) { + return _controls[i]; + } + } + /*device_cat.error() + << "Axis " << axis << " was not found in the controls list\n";*/ + nassertr(false, AnalogState()); +} + /** * Returns true if the state of the indicated analog control is known, or false * if we have never heard anything about this particular control. @@ -302,7 +364,7 @@ get_control_state(int index) const { INLINE bool InputDevice:: is_control_known(int index) const { if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._known; + return _controls[index].known; } else { return false; } @@ -377,8 +439,8 @@ operator < (const InputDevice &) const { */ INLINE InputDevice::ButtonState:: ButtonState() : - _handle(ButtonHandle::none()), - _state(S_unknown) + handle(ButtonHandle::none()), + state(S_unknown) { } @@ -387,8 +449,8 @@ ButtonState() : */ INLINE InputDevice::ButtonState:: ButtonState(ButtonHandle handle) : - _handle(handle), - _state(S_unknown) + handle(handle), + state(S_unknown) { } @@ -397,8 +459,8 @@ ButtonState(ButtonHandle handle) : */ INLINE InputDevice::AnalogState:: AnalogState() : - _axis(C_none), - _state(0.0), - _known(false) + axis(C_none), + state(0.0), + known(false) { } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 04dfc4caae..956a821bef 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -171,12 +171,12 @@ set_button_state(int index, bool down) { } State new_state = down ? S_down : S_up; - if (_buttons[index]._state == new_state) { + if (_buttons[index].state == new_state) { return; } - _buttons[index]._state = new_state; + _buttons[index].state = new_state; - ButtonHandle handle = _buttons[index]._handle; + ButtonHandle handle = _buttons[index].handle; if (device_cat.is_spam()) { device_cat.spam() @@ -208,19 +208,19 @@ set_control_state(int index, double state) { _controls.resize(index + 1, AnalogState()); } - if (device_cat.is_spam() && _controls[index]._state != state) { + if (device_cat.is_spam() && _controls[index].state != state) { device_cat.spam() << "Changed control " << index; - if (_controls[index]._axis != C_none) { - device_cat.spam(false) << " (" << _controls[index]._axis << ")"; + if (_controls[index].known != C_none) { + device_cat.spam(false) << " (" << _controls[index].known << ")"; } device_cat.spam(false) << " to " << state << "\n"; } - _controls[index]._state = state; - _controls[index]._known = true; + _controls[index].state = state; + _controls[index].known = true; } /** @@ -308,13 +308,13 @@ output_buttons(ostream &out) const { Buttons::const_iterator bi; for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { const ButtonState &state = (*bi); - if (state._state != S_unknown) { + if (state.state != S_unknown) { if (any_buttons) { out << ", "; } any_buttons = true; out << (int)(bi - _buttons.begin()) << "="; - if (state._state == S_up) { + if (state.state == S_up) { out << "up"; } else { out << "down"; @@ -336,17 +336,17 @@ write_buttons(ostream &out, int indent_level) const { Buttons::const_iterator bi; for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { const ButtonState &state = (*bi); - if (state._state != S_unknown) { + if (state.state != S_unknown) { any_buttons = true; indent(out, indent_level) << (int)(bi - _buttons.begin()) << ". "; - if (state._handle != ButtonHandle::none()) { - out << "(" << state._handle << ") "; + if (state.handle != ButtonHandle::none()) { + out << "(" << state.handle << ") "; } - if (state._state == S_up) { + if (state.state == S_up) { out << "up"; } else { out << "down"; @@ -372,11 +372,11 @@ write_controls(ostream &out, int indent_level) const { Controls::const_iterator ai; for (ai = _controls.begin(); ai != _controls.end(); ++ai) { const AnalogState &state = (*ai); - if (state._known) { + if (state.known) { any_controls = true; indent(out, indent_level) - << (int)(ai - _controls.begin()) << ". " << state._state << "\n"; + << (int)(ai - _controls.begin()) << ". " << state.state << "\n"; } } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 3d90bc60dd..f036394b92 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -250,13 +250,15 @@ public: S_down }; +PUBLISHED: class ButtonState { public: INLINE ButtonState(); INLINE ButtonState(ButtonHandle handle); - ButtonHandle _handle; - State _state; + PUBLISHED: + ButtonHandle handle; + State state; }; typedef pvector Buttons; Buttons _buttons; @@ -265,9 +267,10 @@ public: public: INLINE AnalogState(); - ControlAxis _axis; - double _state; - bool _known; + PUBLISHED: + ControlAxis axis; + double state; + bool known; }; typedef pvector Controls; Controls _controls; @@ -277,6 +280,16 @@ public: TrackerData _tracker_data; + + INLINE ButtonState get_button(size_t index) const; + INLINE ButtonState find_button(ButtonHandle handle) const; + + INLINE AnalogState get_control(size_t index) const; + INLINE AnalogState find_control(ControlAxis axis) const; + + // Make device controls iterable + MAKE_SEQ_PROPERTY(controls, get_num_controls, get_control); + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index b6b622412b..a1abddf3dd 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -225,7 +225,7 @@ open_device() { handle = ButtonHandle::none(); break; } - _buttons[i]._handle = handle; + _buttons[i].handle = handle; } } @@ -327,7 +327,7 @@ open_device() { axis = C_none; break; } - _controls[i]._axis = axis; + _controls[i].axis = axis; } } @@ -389,7 +389,7 @@ open_device() { // this gamepad yet (which means it hasn't been plugged in for this session) if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) { for (int i = 0; i < _controls.size(); ++i) { - if (_controls[i]._state != 0.0) { + if (_controls[i].state != 0.0) { _is_connected = true; return true; } @@ -453,7 +453,7 @@ process_events() { set_button_state(_dpad_up_button+1, events[i].value > 1000); } - ControlAxis axis = _controls[index]._axis; + ControlAxis axis = _controls[index].axis; if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { // We'd like to use 0.0 to indicate the resting position. From 2565f4b423b3d3c76c8258cae3495af15f1b9e07 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 4 Aug 2016 22:54:19 +0200 Subject: [PATCH 26/82] Renamed gamepad sample script to gamepad.py Renamed gamepad sample script Changed axis usage to the newly created functions and exposed classes --- samples/gamepad/{main.py => gamepad.py} | 28 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) rename samples/gamepad/{main.py => gamepad.py} (73%) diff --git a/samples/gamepad/main.py b/samples/gamepad/gamepad.py similarity index 73% rename from samples/gamepad/main.py rename to samples/gamepad/gamepad.py index 189c4e0f16..fe6cbd296f 100644 --- a/samples/gamepad/main.py +++ b/samples/gamepad/gamepad.py @@ -56,6 +56,16 @@ class App(ShowBase): # movements by ourself self.disableMouse() + # set the center position of the control sticks + # NOTE: here we assume, that the wheel is centered when the application get started. + # In real world applications, you should notice the user and give him enough time + # to center the wheel until you store the center position of the controler! + gamepads = base.devices.getDevices(InputDevice.DC_gamepad) + self.lxcenter = gamepads[0].findControl(InputDevice.C_left_x).state + self.lycenter = gamepads[0].findControl(InputDevice.C_left_y).state + self.rxcenter = gamepads[0].findControl(InputDevice.C_right_x).state + self.rycenter = gamepads[0].findControl(InputDevice.C_right_y).state + self.taskMgr.add(self.moveTask, "movement update task") def connect(self, device): @@ -95,16 +105,14 @@ class App(ShowBase): return task.cont # we will use the first found gamepad - for i in range(gamepads[0].getNumControls()): - if gamepads[0].getControlMap(i) == InputDevice.C_left_x: - movementVec.setX(gamepads[0].getControlState(i)) - elif gamepads[0].getControlMap(i) == InputDevice.C_left_y: - movementVec.setY(gamepads[0].getControlState(i)) - - if gamepads[0].getControlMap(i) == InputDevice.C_right_x: - base.camera.setH(base.camera, 100 * dt * (gamepads[0].getControlState(i))) - elif gamepads[0].getControlMap(i) == InputDevice.C_right_y: - base.camera.setP(base.camera, 100 * dt * (gamepads[0].getControlState(i))) + # Move the camera left/right + movementVec.setX(gamepads[0].findControl(InputDevice.C_left_x).state - self.lxcenter) + # Move the camera forward/backward + movementVec.setY(gamepads[0].findControl(InputDevice.C_left_y).state - self.lycenter) + # Control the cameras heading + base.camera.setH(base.camera, 100 * dt * (gamepads[0].findControl(InputDevice.C_right_x).state - self.rxcenter)) + # Control the cameras pitch + base.camera.setP(base.camera, 100 * dt * (gamepads[0].findControl(InputDevice.C_right_y).state - self.rycenter)) # calculate movement base.camera.setX(base.camera, 100 * dt * movementVec.getX()) From 3da928a4de888aa49ca3cdc914ccc2e34f7a15f2 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 4 Aug 2016 22:55:39 +0200 Subject: [PATCH 27/82] changed axis state detection to new functions and exposed state classes --- samples/gamepad/steeringWheel.py | 37 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py index 6353f24188..256c2e1eb3 100644 --- a/samples/gamepad/steeringWheel.py +++ b/samples/gamepad/steeringWheel.py @@ -55,12 +55,14 @@ class App(ShowBase): self.environment = loader.loadModel("environment") self.environment.reparentTo(render) + # save the center position of the wheel + # NOTE: here we assume, that the wheel is centered when the application get started. + # In real world applications, you should notice the user and give him enough time + # to center the wheel until you store the center position of the controler! self.wheelCenter = 0 wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) if len(wheels) > 0: - for i in range(wheels[0].getNumControls()): - if wheels[0].getControlMap(i) == InputDevice.C_wheel: - self.wheelCenter = wheels[0].getControlState(i) + self.wheelCenter = wheels[0].findControl(InputDevice.C_wheel).state # disable pandas default mouse-camera controls so we can handle the camera # movements by ourself @@ -111,24 +113,21 @@ class App(ShowBase): self.currentMoveSpeed = 0 # we will use the first found wheel - for i in range(wheels[0].getNumControls()): + # Acclerate + accleration = wheels[0].findControl(InputDevice.C_accelerator).state * self.maxAccleration + if self.currentMoveSpeed > wheels[0].findControl(InputDevice.C_accelerator).state * self.maxSpeed: + self.currentMoveSpeed -= dt * self.deaccleration + self.currentMoveSpeed += dt * accleration - if wheels[0].getControlMap(i) == InputDevice.C_accelerator: - accleration = wheels[0].getControlState(i) * self.maxAccleration - self.currentMoveSpeed += dt * accleration + # Break + deacleration = wheels[0].findControl(InputDevice.C_brake).state * self.deaclerationBreak + self.currentMoveSpeed -= dt * deacleration + if self.currentMoveSpeed < 0: + self.currentMoveSpeed = 0 - if self.currentMoveSpeed > wheels[0].getControlState(i) * self.maxSpeed: - self.currentMoveSpeed -= dt * self.deaccleration - elif wheels[0].getControlMap(i) == InputDevice.C_brake: - deacleration = wheels[0].getControlState(i) * self.deaclerationBreak - self.currentMoveSpeed -= dt * deacleration - - elif self.currentMoveSpeed < 0: - self.currentMoveSpeed = 0 - - if wheels[0].getControlMap(i) == InputDevice.C_wheel: - rotation = self.wheelCenter - wheels[0].getControlState(i) - base.camera.setH(base.camera, 100 * dt * rotation) + # Steering + rotation = self.wheelCenter - wheels[0].findControl(InputDevice.C_wheel).state + base.camera.setH(base.camera, 100 * dt * rotation) # calculate movement base.camera.setY(base.camera, dt * self.currentMoveSpeed) From 5a378becfc35f01f7d6182bb05fbcfc0ed994847 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 13:11:24 +0200 Subject: [PATCH 28/82] Added button mapping GUI --- samples/gamepad/mappingGUI.py | 342 ++++++++++++++++++++++ samples/gamepad/models/button_map.egg | 117 ++++++++ samples/gamepad/models/click.png | Bin 0 -> 30591 bytes samples/gamepad/models/dec_click.png | Bin 0 -> 847 bytes samples/gamepad/models/dec_disabled.png | Bin 0 -> 852 bytes samples/gamepad/models/dec_hover.png | Bin 0 -> 831 bytes samples/gamepad/models/dec_map.egg | 117 ++++++++ samples/gamepad/models/dec_ready.png | Bin 0 -> 781 bytes samples/gamepad/models/dialog.png | Bin 0 -> 4325 bytes samples/gamepad/models/disabled.png | Bin 0 -> 23853 bytes samples/gamepad/models/hover.png | Bin 0 -> 27590 bytes samples/gamepad/models/inc_click.png | Bin 0 -> 843 bytes samples/gamepad/models/inc_disabled.png | Bin 0 -> 866 bytes samples/gamepad/models/inc_hover.png | Bin 0 -> 835 bytes samples/gamepad/models/inc_map.egg | 117 ++++++++ samples/gamepad/models/inc_ready.png | Bin 0 -> 794 bytes samples/gamepad/models/li_ready_even.png | Bin 0 -> 186 bytes samples/gamepad/models/li_ready_odd.png | Bin 0 -> 186 bytes samples/gamepad/models/list_item_even.egg | 33 +++ samples/gamepad/models/list_item_odd.egg | 33 +++ samples/gamepad/models/ready.png | Bin 0 -> 25376 bytes samples/gamepad/models/thumb_click.png | Bin 0 -> 916 bytes samples/gamepad/models/thumb_disabled.png | Bin 0 -> 912 bytes samples/gamepad/models/thumb_hover.png | Bin 0 -> 892 bytes samples/gamepad/models/thumb_map.egg | 117 ++++++++ samples/gamepad/models/thumb_ready.png | Bin 0 -> 815 bytes 26 files changed, 876 insertions(+) create mode 100644 samples/gamepad/mappingGUI.py create mode 100644 samples/gamepad/models/button_map.egg create mode 100644 samples/gamepad/models/click.png create mode 100644 samples/gamepad/models/dec_click.png create mode 100644 samples/gamepad/models/dec_disabled.png create mode 100644 samples/gamepad/models/dec_hover.png create mode 100644 samples/gamepad/models/dec_map.egg create mode 100644 samples/gamepad/models/dec_ready.png create mode 100644 samples/gamepad/models/dialog.png create mode 100644 samples/gamepad/models/disabled.png create mode 100644 samples/gamepad/models/hover.png create mode 100644 samples/gamepad/models/inc_click.png create mode 100644 samples/gamepad/models/inc_disabled.png create mode 100644 samples/gamepad/models/inc_hover.png create mode 100644 samples/gamepad/models/inc_map.egg create mode 100644 samples/gamepad/models/inc_ready.png create mode 100644 samples/gamepad/models/li_ready_even.png create mode 100644 samples/gamepad/models/li_ready_odd.png create mode 100644 samples/gamepad/models/list_item_even.egg create mode 100644 samples/gamepad/models/list_item_odd.egg create mode 100644 samples/gamepad/models/ready.png create mode 100644 samples/gamepad/models/thumb_click.png create mode 100644 samples/gamepad/models/thumb_disabled.png create mode 100644 samples/gamepad/models/thumb_hover.png create mode 100644 samples/gamepad/models/thumb_map.egg create mode 100644 samples/gamepad/models/thumb_ready.png diff --git a/samples/gamepad/mappingGUI.py b/samples/gamepad/mappingGUI.py new file mode 100644 index 0000000000..ce9234bd1d --- /dev/null +++ b/samples/gamepad/mappingGUI.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python +''' +Demonstrate how a simple button mapping gui can be written +''' + +from direct.showbase.ShowBase import ShowBase +from direct.gui.DirectGui import ( + DGG, + DirectFrame, + DirectButton, + DirectLabel, + OkCancelDialog, + DirectScrolledFrame) +from panda3d.core import ( + VBase4, + TextNode, + Vec2, + InputDevice, + loadPrcFileData) + +# Make sure the textures look crisp on every device that supports +# non-power-2 textures +loadPrcFileData("", "textures-auto-power-2 #t") + +class App(ShowBase): + def __init__(self): + ShowBase.__init__(self) + + self.setBackgroundColor(0, 0, 0) + # make the font look nice at a big scale + DGG.getDefaultFont().setPixelsPerUnit(100) + + # a dict of actions and button/axis events + self.gamepadMapping = { + "Move forward":"Left Stick Y", + "Move backward":"Left Stick Y", + "Move left":"Left Stick X", + "Move right":"Left Stick X", + "Jump":"a", + "Action":"b", + "Sprint":"x", + "Map":"y", + "action-1":"c", + "action-2":"d", + "action-3":"e", + "action-4":"f", + "action-5":"g", + "action-6":"h", + "action-7":"i", + "action-8":"j", + "action-9":"k", + "action-10":"l", + "action-11":"m", + } + # this will store the action that we want to remap + self.actionToMap = "" + # this will store the key/axis that we want to asign to an action + self.newActionKey = "" + # this will store the label that needs to be actualized in the list + self.actualizeLabel = None + + # The geometry for our basic buttons + maps = loader.loadModel("models/button_map") + self.buttonGeom = ( + maps.find("**/ready"), + maps.find("**/click"), + maps.find("**/hover"), + maps.find("**/disabled")) + + # Create the dialog that asks the user for input on a given + # action to map a key to. + DGG.setDefaultDialogGeom("models/dialog.png") + # setup a dialog to ask for device input + self.dlgInput = OkCancelDialog( + dialogName="dlg_device_input", + pos=(0, 0, 0.25), + text="Hit desired key:", + text_fg=VBase4(0.898, 0.839, 0.730, 1.0), + text_shadow=VBase4(0, 0, 0, 0.75), + text_shadowOffset=Vec2(0.05, 0.05), + text_scale=0.05, + text_align=TextNode.ACenter, + fadeScreen=0.65, + frameColor=VBase4(0.3, 0.3, 0.3, 1), + button_geom=self.buttonGeom, + button_scale=0.15, + button_text_scale=0.35, + button_text_align=TextNode.ALeft, + button_text_fg=VBase4(0.898, 0.839, 0.730, 1.0), + button_text_pos=Vec2(-0.9, -0.125), + button_relief=1, + button_pad=Vec2(0.01, 0.01), + button_frameColor=VBase4(0, 0, 0, 0), + button_frameSize=VBase4(-1.0, 1.0, -0.25, 0.25), + button_pressEffect=False, + command=self.closeDialog) + self.dlgInput.setTransparency(True) + self.dlgInput.configureDialog() + scale = self.dlgInput["image_scale"] + self.dlgInput["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0) + self.dlgInput["text_pos"] = (self.dlgInput["text_pos"][0], self.dlgInput["text_pos"][1] + 0.06) + self.dlgInput.hide() + + # create a sample title + self.textscale = 0.1 + self.title = DirectLabel( + scale=self.textscale, + pos=(base.a2dLeft + 0.05, 0.0, base.a2dTop - (self.textscale + 0.05)), + frameColor=VBase4(0, 0, 0, 0), + text="Button Mapping", + text_align=TextNode.ALeft, + text_fg=VBase4(1, 1, 1, 1), + text_shadow=VBase4(0, 0, 0, 0.75), + text_shadowOffset=Vec2(0.05, 0.05)) + self.title.setTransparency(1) + + # Set up the list of actions that we can map keys to + # create a frame that will create the scrollbars for us + # Load the models for the scrollbar elements + thumbMaps = loader.loadModel("models/thumb_map") + thumbGeom = ( + thumbMaps.find("**/thumb_ready"), + thumbMaps.find("**/thumb_click"), + thumbMaps.find("**/thumb_hover"), + thumbMaps.find("**/thumb_disabled")) + incMaps = loader.loadModel("models/inc_map") + incGeom = ( + incMaps.find("**/inc_ready"), + incMaps.find("**/inc_click"), + incMaps.find("**/inc_hover"), + incMaps.find("**/inc_disabled")) + decMaps = loader.loadModel("models/dec_map") + decGeom = ( + decMaps.find("**/dec_ready"), + decMaps.find("**/dec_click"), + decMaps.find("**/dec_hover"), + decMaps.find("**/dec_disabled")) + # create the scrolled frame that will hold our list + self.lstActionMap = DirectScrolledFrame( + # make the frame occupy the whole window + frameSize=VBase4(base.a2dLeft, base.a2dRight, 0.0, 1.55), + # make the canvas as big as the frame + canvasSize=VBase4(base.a2dLeft, base.a2dRight, 0.0, 0.0), + # set the frames color to white + frameColor=VBase4(0, 0, 0.25, 0.75), + pos=(0, 0, -0.8), + + verticalScroll_scrollSize=0.2, + verticalScroll_frameColor=VBase4(0.02, 0.02, 0.02, 1), + + verticalScroll_thumb_relief=1, + verticalScroll_thumb_geom=thumbGeom, + verticalScroll_thumb_pressEffect=False, + verticalScroll_thumb_frameColor=VBase4(0, 0, 0, 0), + + verticalScroll_incButton_relief=1, + verticalScroll_incButton_geom=incGeom, + verticalScroll_incButton_pressEffect=False, + verticalScroll_incButton_frameColor=VBase4(0, 0, 0, 0), + + verticalScroll_decButton_relief=1, + verticalScroll_decButton_geom=decGeom, + verticalScroll_decButton_pressEffect=False, + verticalScroll_decButton_frameColor=VBase4(0, 0, 0, 0),) + + # creat the list items + idx = 0 + self.listBGEven = base.loader.loadModel("models/list_item_even") + self.listBGOdd = base.loader.loadModel("models/list_item_odd") + for key, value in self.gamepadMapping.items(): + item = self.__makeListItem(key, key, value, idx) + item.reparentTo(self.lstActionMap.getCanvas()) + idx += 1 + + # recalculate the canvas size to set scrollbars if necesary + self.lstActionMap["canvasSize"] = ( + base.a2dLeft+0.05, base.a2dRight-0.05, + -(len(self.gamepadMapping.keys())*0.1), 0.09) + self.lstActionMap.setCanvasSize() + + def closeDialog(self, result): + self.dlgInput.hide() + if result == DGG.DIALOG_OK: + # map the event to the given action + self.gamepadMapping[self.actionToMap] = self.newActionKey + # actualize the label in the list that shows the current + # event for the action + self.actualizeLabel["text"] = self.newActionKey + + # cleanup + self.dlgInput["text"] ="Hit desired key:" + self.actionToMap = "" + self.newActionKey = "" + self.actualizeLabel = None + for bt in base.buttonThrowers: + bt.node().setButtonDownEvent("") + for bt in base.deviceButtonThrowers: + bt.node().setButtonDownEvent("") + taskMgr.remove("checkControls") + + def changeMapping(self, action, label): + # set the action that we want to map a new key to + self.actionToMap = action + # set the label that needs to be actualized + self.actualizeLabel = label + # show our dialog + self.dlgInput.show() + + # catch all button events + for bt in base.buttonThrowers: + bt.node().setButtonDownEvent("keyListenEvent") + for bt in base.deviceButtonThrowers: + bt.node().setButtonDownEvent("deviceListenEvent") + self.setKeyCalled = False + self.accept("keyListenEvent", self.setKey) + self.accept("deviceListenEvent", self.setDeviceKey) + + # As there are no events thrown for control changes, we set up + # a task to check if the controls got moved + # This list will help us for checking which controls got moved + self.controlStates = {None:{}} + # fill it with all available controls + for device in base.devices.get_devices(): + for ctrl in device.controls: + if device not in self.controlStates.keys(): + self.controlStates.update({device: {ctrl.axis: ctrl.state}}) + else: + self.controlStates[device].update({ctrl.axis: ctrl.state}) + # start the task + taskMgr.add(self.watchControls, "checkControls") + + def watchControls(self, task): + # move through all devices and all it's controls + for device in base.devices.get_devices(): + for ctrl in device.controls: + # if a control got changed more than the given puffer zone + if self.controlStates[device][ctrl.axis] + 0.2 < ctrl.state or \ + self.controlStates[device][ctrl.axis] - 0.2 > ctrl.state: + # set the current state in the dict + self.controlStates[device][ctrl.axis] = ctrl.state + # check which axis got moved + if ctrl.axis == InputDevice.C_left_x: + self.setKey("Left Stick X") + elif ctrl.axis == InputDevice.C_left_y: + self.setKey("Left Stick Y") + elif ctrl.axis == InputDevice.C_left_trigger: + self.setKey("Left Trigger") + elif ctrl.axis == InputDevice.C_right_x: + self.setKey("Right Stick X") + elif ctrl.axis == InputDevice.C_right_y: + self.setKey("Right Stick Y") + elif ctrl.axis == InputDevice.C_right_trigger: + self.setKey("Right Trigger") + elif ctrl.axis == InputDevice.C_x: + self.setKey("X") + elif ctrl.axis == InputDevice.C_y: + self.setKey("Y") + elif ctrl.axis == InputDevice.C_trigger: + self.setKey("Trigger") + elif ctrl.axis == InputDevice.C_throttle: + self.setKey("Throttle") + elif ctrl.axis == InputDevice.C_rudder: + self.setKey("Rudder") + elif ctrl.axis == InputDevice.C_hat_x: + self.setKey("Hat X") + elif ctrl.axis == InputDevice.C_hat_y: + self.setKey("Hat Y") + elif ctrl.axis == InputDevice.C_wheel: + self.setKey("Wheel") + elif ctrl.axis == InputDevice.C_accelerator: + self.setKey("Acclerator") + elif ctrl.axis == InputDevice.C_brake: + self.setKey("Break") + return task.cont + + def setKey(self, args): + self.setKeyCalled = True + if self.dlgInput.buttonList[0].guiItem.getState() == 1: + # this occurs if the OK button was clicked. To prevent to + # always set the mouse1 event whenever the OK button was + # pressed, we instantly return from this function + return + self.dlgInput["text"] = "New event will be:\n\n" + args + self.newActionKey = args + + def setDeviceKey(self, args): + if not self.setKeyCalled: + self.setKey(args) + self.setKeyCalled = False + + def __makeListItem(self, itemName, action, event, index): + def dummy(): pass + if index % 2 == 0: + bg = self.listBGEven + else: + bg = self.listBGOdd + item = DirectFrame( + text=itemName, + geom=bg, + geom_scale=(base.a2dRight-0.05, 1, 0.1), + frameSize=VBase4(base.a2dLeft+0.05, base.a2dRight-0.05, -0.05, 0.05), + frameColor=VBase4(1,0,0,0), + text_align=TextNode.ALeft, + text_scale=0.05, + text_fg=VBase4(1,1,1,1), + text_pos=(base.a2dLeft + 0.3, -0.015), + text_shadow=VBase4(0, 0, 0, 0.35), + text_shadowOffset=Vec2(-0.05, -0.05), + pos=(0.05, 0, -(0.10 * index))) + item.setTransparency(True) + lbl = DirectLabel( + text=event, + text_fg=VBase4(1, 1, 1, 1), + text_scale=0.05, + text_pos=Vec2(0, -0.015), + frameColor=VBase4(0, 0, 0, 0), + ) + lbl.reparentTo(item) + lbl.setTransparency(True) + buttonScale = 0.15 + btn = DirectButton( + text="Change", + geom=self.buttonGeom, + scale=buttonScale, + text_scale=0.25, + text_align=TextNode.ALeft, + text_fg=VBase4(0.898, 0.839, 0.730, 1.0), + text_pos=Vec2(-0.9, -0.085), + relief=1, + pad=Vec2(0.01, 0.01), + frameColor=VBase4(0, 0, 0, 0), + frameSize=VBase4(-1.0, 1.0, -0.25, 0.25), + pos=(base.a2dRight-(0.898*buttonScale+0.3), 0, 0), + pressEffect=False, + command=self.changeMapping, + extraArgs=[action, lbl]) + btn.setTransparency(True) + btn.reparentTo(item) + return item + +app = App() +app.run() diff --git a/samples/gamepad/models/button_map.egg b/samples/gamepad/models/button_map.egg new file mode 100644 index 0000000000..75a305e0da --- /dev/null +++ b/samples/gamepad/models/button_map.egg @@ -0,0 +1,117 @@ + { + "egg-texture-cards -o button_map.egg -p 768,192 -g -1.0,1.0,-0.25,0.25 -wm clamp ready.png click.png hover.png disabled.png" +} + ready { + ready.png + wrap { clamp } +} + hover { + hover.png + wrap { clamp } +} + disabled { + disabled.png + wrap { clamp } +} + click { + click.png + wrap { clamp } +} + { + { 1 } + fps { 2 } + vpool { + 0 { + -1 0.25 0 + { 0 1 } + } + 1 { + -1 -0.25 0 + { 0 0 } + } + 2 { + 1 -0.25 0 + { 1 0 } + } + 3 { + 1 0.25 0 + { 1 1 } + } + 4 { + -1 0.25 0 + { 0 1 } + } + 5 { + -1 -0.25 0 + { 0 0 } + } + 6 { + 1 -0.25 0 + { 1 0 } + } + 7 { + 1 0.25 0 + { 1 1 } + } + 8 { + -1 0.25 0 + { 0 1 } + } + 9 { + -1 -0.25 0 + { 0 0 } + } + 10 { + 1 -0.25 0 + { 1 0 } + } + 11 { + 1 0.25 0 + { 1 1 } + } + 12 { + -1 0.25 0 + { 0 1 } + } + 13 { + -1 -0.25 0 + { 0 0 } + } + 14 { + 1 -0.25 0 + { 1 0 } + } + 15 { + 1 0.25 0 + { 1 1 } + } + } + ready { + { + { 1 1 1 1 } + { ready } + { 0 1 2 3 { vpool } } + } + } + click { + { + { 1 1 1 1 } + { click } + { 4 5 6 7 { vpool } } + } + } + hover { + { + { 1 1 1 1 } + { hover } + { 8 9 10 11 { vpool } } + } + } + disabled { + { + { 1 1 1 1 } + { disabled } + { 12 13 14 15 { vpool } } + } + } +} diff --git a/samples/gamepad/models/click.png b/samples/gamepad/models/click.png new file mode 100644 index 0000000000000000000000000000000000000000..fbd06a4df3bbc3d8e522a57cbed450ea9b2de610 GIT binary patch literal 30591 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 e?fGj10Dl3^n9EyoPuV>H0000e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00O5;L_t(o!^M|BNE>k!$G`9Q z(_E@Kjpko-F?U7~N}%BAC@7soK_Q2F;*o$R90;L@ zK&R7rKfoAU^u>wx%2Iy66yohq;J6L^?t~}5lq9k=qP?;Qvwz7LTcl#K_ys_m=<5t{ z^;gQuX_ToaYL$UxOv*PW0Ir#T;D}19lm~9qwU_e1^-2r?h_hRl!o9RdZO84q=ZoEU z2CKQEC`?fl77XCN2J2-n&1Umuv)Rn`@{eZ }e3R4Te@nnv&}gaEL+yPHd=)A3X) z6?Ysb+gAY2Ihm%Z`|leY8^++^AoUHvahz-}mmBimGnvebdcB_Q3jkw`*4EbahwGMQ z8H_QC9H3sWX9|VF@Wb_dK0maxvy+JwfQVpOmhqV1w(Y=K2n0~ARx=Y56T^@B^ZER+ zZQJR{0BqaVpNgr~Y6cO753sehl{O4ReJZY4EG9NLH&dYlRI62^+t}^x?a6Ml&=tV? z`ucdOR8qSI&~;rkO>;anfLg6)Jmb1fh61p)V%-;g@46Nz^J6&1Qi^_hwE!8xTSqan5J_aS2$~82k7-s?ki! z52wDC5aQ^+52qXe+U@p_L?ZEvh(?Gg+0FIraXkO*x^4>q{s9I*$&>iDcXj{(002ov JPDHLkV1fZKcS-;N literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/dec_map.egg b/samples/gamepad/models/dec_map.egg new file mode 100644 index 0000000000..71190d251a --- /dev/null +++ b/samples/gamepad/models/dec_map.egg @@ -0,0 +1,117 @@ + { + "egg-texture-cards -o dec_map.egg -p 32,32 -g -0.04,0.04,-0.04,0.04 -wm clamp dec_ready.png dec_click.png dec_hover.png dec_disabled.png" +} + dec_ready { + dec_ready.png + wrap { clamp } +} + dec_hover { + dec_hover.png + wrap { clamp } +} + dec_disabled { + dec_disabled.png + wrap { clamp } +} + dec_click { + dec_click.png + wrap { clamp } +} + { + { 1 } + fps { 2 } + vpool { + 0 { + -0.04 0.04 0 + { 0 1 } + } + 1 { + -0.04 -0.04 0 + { 0 0 } + } + 2 { + 0.04 -0.04 0 + { 1 0 } + } + 3 { + 0.04 0.04 0 + { 1 1 } + } + 4 { + -0.04 0.04 0 + { 0 1 } + } + 5 { + -0.04 -0.04 0 + { 0 0 } + } + 6 { + 0.04 -0.04 0 + { 1 0 } + } + 7 { + 0.04 0.04 0 + { 1 1 } + } + 8 { + -0.04 0.04 0 + { 0 1 } + } + 9 { + -0.04 -0.04 0 + { 0 0 } + } + 10 { + 0.04 -0.04 0 + { 1 0 } + } + 11 { + 0.04 0.04 0 + { 1 1 } + } + 12 { + -0.04 0.04 0 + { 0 1 } + } + 13 { + -0.04 -0.04 0 + { 0 0 } + } + 14 { + 0.04 -0.04 0 + { 1 0 } + } + 15 { + 0.04 0.04 0 + { 1 1 } + } + } + dec_ready { + { + { 1 1 1 1 } + { dec_ready } + { 0 1 2 3 { vpool } } + } + } + dec_click { + { + { 1 1 1 1 } + { dec_click } + { 4 5 6 7 { vpool } } + } + } + dec_hover { + { + { 1 1 1 1 } + { dec_hover } + { 8 9 10 11 { vpool } } + } + } + dec_disabled { + { + { 1 1 1 1 } + { dec_disabled } + { 12 13 14 15 { vpool } } + } + } +} diff --git a/samples/gamepad/models/dec_ready.png b/samples/gamepad/models/dec_ready.png new file mode 100644 index 0000000000000000000000000000000000000000..a097c1e5cb01030cdfa5cfc500539f58dccbcf39 GIT binary patch literal 781 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 literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/disabled.png b/samples/gamepad/models/disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..9b9dfe50146434aa74d780fc8573eda730b33fed GIT binary patch literal 23853 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

?7%T#3@p?|Qn*cw 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 z z>w6wIP2MFk>^nRUXJ;W(&6SH``adE?UzRrMKW+NsI9fd$#m$9+s9rydUhrgtF z#Dv~o)NpH$?fm6LJNaQ)H-QS1R7;Wpb!$L51m!gOmAc&!L0zlvzBd-&;s<>E`MgyV zPw-S6D4avEuOJr4M76rOe=_B11yx@fy2?s%dY&3pWODKHQ7q51I_%^#h<^{H1)sB@EWyM(6p^Z>nBNt|5Ya zru|%+6iIcXJoKDTNrK-P;b(Y5eJ&6~ACoymjiQX(pJO@OjZc9VqHfV5QSByGsQrV% zvtq8SQFMcN`NfJSgxx2QhQg$3dVmJqk2ATLA|$83tC0({xkdy`S)ySZgD?u8%KI)u z+s9|)tM%R>ikAaT3g>IHX!sSMqq@B#!-o|ZfY9iA6Pd+=`yV)MqdIMZJvv`oRyhi4T(d)_p!LF5z@F*{ zIcfBBR|#g`mqx5tmb^kP+&ai_Z)Mywokl5)7-jgtryyeYgq#S=BhfgtL;R9^>1#SZ zXUTbFPJ-E9y>Q=b)4DsZwhfp54)SNbh-4%qluQwpNPtX>@X2%s$*`j*MqvFOn>+Tl z|NSB9?rSYZ-w|T0Y5&TPA#;fnqyenBZY#!f!XsX|#Hnd_7|SVXqRF2H82@zgt(z>Z z79uTMDy?6SMl3W9e;c3Bmpnea?t426wc1TLL-_unOf&Dr;`}nvqXLdzIrxVkRQxgpko`906ut0dd z&S*$052OB#@14=GI8({9tr8h5dZ}X=y>E2Uzw(G+Xuc3ti_CUU?ID5nD5+g;mc0%$ zh19fa{C+<@|D-ob;9IuoRCkn?VRqi!yhd!2=MbOYea1@*OX?TPk=CrGh8Epa(|*7G z<|K8tkqnb(``3JhS&(^7%jR6qa6uL02hVZ=UblBHR{=Awt?vvqXZ!EPQu5EVl`t^3 z%miVpKiu6hrhnt@PxlUQQU80hr}n-hBv|{fV1)JmE2|BxYqW;xO7T&FfE>+|tCSG( zQrEKR)t$`HL&smXC%2d8qJO-^ct4ds8$2yuWg74;JCu7Zo!4yrV%FOAO&weMhSTv5 zG9EIOx4Sx%Uxx1c)LoRSn(5Qb?O#*ZPy*pJuil$V2`wY9uMWE;-#5&CrXZf@^kw+K zpajw1a!Ve+_Or6RUs^1LTemc%XowD(Acfgoh7icpYncW%-==%`LOZ*Ucm3rK2Hlrh zhPt96joZ#c9^gGHjo7s3t$UK`b;$$j`WKUVekGzRQ{0+R<9%FWOwrcBkKTqIU8Rfd zoT13OK+&yMKx+2q;PL4N+9Kh}S@V2KgZ z?m0;av~P@!0$$>iRlD@yk|3;qXUbTt9%~o{* ztSfU$uw|(luQE{WCy)ta>L)xD4u#ZI`M&y!n6EB#>K>puMC*y>$ Q^jZMe zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00Of~L_t(o!^M}+PZLoP$G@|? zAw(#0BC-TE#slC*5)(OMxN;yK-ka~|y_wm(A^b2|o){AZX`DzL0H_^48FPKtw(BdJ zrmO=10YG2h<7a}<_5_z`wp|UT)1j9n%M)Xw*gg#aL=!#0h)i^=yY-S)i}}a|-fGN> z4gi)_nUMrR8b1>!njB)@(`|cp7uYshC*C0f!vUgEnP_sz35B8yf;28UF$aJVnR!oV zp83%F)0Zt^+hE={fN20DGN9;0;(&0La74WU=A`C#?3t4qQE$K*QM>SKs!arTO#jnw z$8@wD6V6|i-}bwP)M)wa?2G&T{Wm>T#o^(BF*Wsk_g`y-Mo#ADR`M(l0Koiw?2B7~#l=KZH`f%v`ubWy*E3C5$no)!nMk}Yv<$%Q zErddAP3KM10P%P%pUvuD8?DP^j?Gw1&9xlBiEF&!$rL4#a^uq#fU2swLLpziXg-_O z%h6~w>l*+wv!Yxa-$>+*>wkvec0)M4oXzL+r+7F2G}(qRcUQw^6)y* z(>p}>dVu(6;8U5|dVl4u*_Ah^A^@1?$9J8bcMplsMMT{INqz}C?RoW;rY!{l;5%R@ VR9zbe zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00PQML_t(o!^M}+OB+!T$G=Hd zB89YyRLTaV3W61aZMNP#2zvAu@DJ#%|3i;eP)LXhDUlL_2tpv9daWm+2a}MpwIMNS z;?iAowI*#E&6@0cJ#^ESv^BeHR_FG9&3tCwyxkcae#;DiIRIk-dSRWU0lWe5765G8 zjV(yX}3W zQCCYPzVg$lXPT{D8Nismm2fQE?@A{3B&#nzncS0Dw%?@lEuZvL$luMIolCndwaXg z^ZYl%pSyJWYdjt=UmE}b78VvtMgf+Wmvu!>7eFi)JIiLXx?jlt{(g09YwN6Q0F+Wg zQPAtXZU6w-+}!+<%jLdz+Ly^>sv8>{r(Fl2lwxUVsiS9pzh5wtD|REBqtWPTu~@AA zZ#|#S*CLU~iDdwU5PH@91_FUYN+~P@0D$%N^^;<;c-}r=C=_bpaQMWiKEnWn5E=*s z{))oN%8Eb;p_T&xKsX$hMNzzHjRiqC4~0Uq8C|9T8jS|^`~AP7u)4Y`sH#eD3;+N^ zp^$uZbX1olslK+hc5GG;WA>n;C?psR9s;mkUS8502LOOzFnEk>3e#S_8mWpDMImM? zvd(>rhN#=Hsx)i0U{s~YA07)uWIc}1*8i3;_SxG9le(iM!fTsW+5aJ}$ z(@!1_42;~lxM(&i6}jHc#$;&d(Y=|Oxe2pQn!Y{Iv#vA6G|9nUhpalK=n!07*qoM6N<$f;{GVA^-pY literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/inc_hover.png b/samples/gamepad/models/inc_hover.png new file mode 100644 index 0000000000000000000000000000000000000000..089df4352e97272aaeafc924318056af27553007 GIT binary patch literal 835 zcmV-J1HAl+P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00OH?L_t(o!^M}sPuoxs$G`U+ zwG0GhVlZN00%c-kL;`g}bYtQlsM?*SV|OGZD*=_r2m?wakfab$QcB~5INW6Wo!|H4d-tC22u)K}bxH^^LquZ$`Vf#Yb4E(}(Xy;P z03ZMu8X9^fgqTAQO-h+66bdm?Rdq^Hl(zst^m-6@sS=Gvx-aRt%vl@QwV1Us0JyIE zE+mAQxe+H?h%$dEOZg@Pd^qo(_<#aThKc4?qJ^lp6k1sbF+;<{!=C{35q%y9&Kt~g z-+~N1Q7Q^Vo-x130H}+bgd-XWGaFXWYtL+0L?hwmZJ)RudaeNbcJS%9Z+lvviMxXr zZtwtiHA1c2)9Lj1=;&xiyW;Th@O*lDI`cmt(Q1-dEM|84t$yBZ39z%Xb9Q`ue9^A> z(b3V5?d|QeM*{#rJRbkvF2L&Qs;|iT0!Sv4r@36t_YTQsv-alZ=4saem>DZ8E54(U zNF;pcjqvNZzP?^NIXU^+%Fi@Sdu?s4+I0YC_O-6MuIJjx7155fuItrev3U8=cs`%M zOe7MOzyPF_tn2!NQ7{a{WM%{g007I&%avlWc=h-5LZNUOkH;(R&et}8l#&g@xEF=3 zt*xAtk^={59!%v*rSj|MzEmn*X_{B%^aSAg`kFU3Hq1W`s+{Y(+%o_G&@`=FtJUh& zYPG(!v{dSJo=~SRI*ucg$)pK@8jS|`8~^|o7Z*!-q=oKRul}nd$8lt574koGz;S`e zaPTGBWZ2WX1*m&xeby$LSA&)(npeFB1prVN%zWTo?^;9?gI?V}+vUz^KmleWM5!pz z#2~P1HGgmiPu`k6d2^}&fNk4f`}_OzL^MD|k;iUNAMN>j%d%1c@EbkpOpR*L>&^fG N002ovPDHLkV1kfOcRv6C literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/inc_map.egg b/samples/gamepad/models/inc_map.egg new file mode 100644 index 0000000000..9bd07862da --- /dev/null +++ b/samples/gamepad/models/inc_map.egg @@ -0,0 +1,117 @@ + { + "egg-texture-cards -o inc_map.egg -p 32,32 -g -0.04,0.04,-0.04,0.04 -wm clamp inc_ready.png inc_click.png inc_hover.png inc_disabled.png" +} + inc_ready { + inc_ready.png + wrap { clamp } +} + inc_hover { + inc_hover.png + wrap { clamp } +} + inc_disabled { + inc_disabled.png + wrap { clamp } +} + inc_click { + inc_click.png + wrap { clamp } +} + { + { 1 } + fps { 2 } + vpool { + 0 { + -0.04 0.04 0 + { 0 1 } + } + 1 { + -0.04 -0.04 0 + { 0 0 } + } + 2 { + 0.04 -0.04 0 + { 1 0 } + } + 3 { + 0.04 0.04 0 + { 1 1 } + } + 4 { + -0.04 0.04 0 + { 0 1 } + } + 5 { + -0.04 -0.04 0 + { 0 0 } + } + 6 { + 0.04 -0.04 0 + { 1 0 } + } + 7 { + 0.04 0.04 0 + { 1 1 } + } + 8 { + -0.04 0.04 0 + { 0 1 } + } + 9 { + -0.04 -0.04 0 + { 0 0 } + } + 10 { + 0.04 -0.04 0 + { 1 0 } + } + 11 { + 0.04 0.04 0 + { 1 1 } + } + 12 { + -0.04 0.04 0 + { 0 1 } + } + 13 { + -0.04 -0.04 0 + { 0 0 } + } + 14 { + 0.04 -0.04 0 + { 1 0 } + } + 15 { + 0.04 0.04 0 + { 1 1 } + } + } + inc_ready { + { + { 1 1 1 1 } + { inc_ready } + { 0 1 2 3 { vpool } } + } + } + inc_click { + { + { 1 1 1 1 } + { inc_click } + { 4 5 6 7 { vpool } } + } + } + inc_hover { + { + { 1 1 1 1 } + { inc_hover } + { 8 9 10 11 { vpool } } + } + } + inc_disabled { + { + { 1 1 1 1 } + { inc_disabled } + { 12 13 14 15 { vpool } } + } + } +} diff --git a/samples/gamepad/models/inc_ready.png b/samples/gamepad/models/inc_ready.png new file mode 100644 index 0000000000000000000000000000000000000000..9d4c153e44d153e92a9de9c853bc5c2eb2f01b6d GIT binary patch literal 794 zcmV+#1LgdQP)=0>l6S02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00M$ZL_t(o!^M}uOB+EL#eW-P z)kDOS2XoR>u@@oKv*4|j_SSzvn*T!~fekUxmV}}rf)I#@QthcU*J6-BNHt>ExD=sk z)tYKeY-W0xq)TdRc4M}FFtE(be(!y6W_I6bSTPAq0r!Dk+>0#m7I+V!)jOX9&-lNQ z1YR{N|Dakh4;v>SW#`+)PnA2ExA2(s)@28RgYaS$)(_xm7T%n8uGs!AnDD{$kir0X zc>qjlz&o|(8)I-#g~y-VReo(02JgYcEO6oa_XJ_}?t<}!EEeWiy zuAby_xiiO~yKMGbDwR69H35K`nVFJP0^xAjmU6ZTB$LVGLZM*$gyi%2^2*A}amNTq zDIKX`w|d(I0G5}RzwYepd~Y>xYip~#w6t{8aRO3GLZML0%5+^fo$M8llgW#Vi${iG zod0WlcX#)Eet!PYH3C8idDHqvB9T2QC9V+wNF)-6hGAU%*}x_(so|z?(WtM!x;Db{e1wYX+aRQE2>l~qCJQF*JJpjrmVBuG2v5K7Jae4el@%E%V}drW`NH?KQM6H<7wl1{<=

QL70(Y)*K0-AbW|YuPgfl4h|kwojQL70(Y)*K0-AbW|YuPgfl4h|kQ)~UZ@4g-ZGOI#yLobz*YQ}ap~oQqNuOHxx5 z$}>wc6x=<11Hv2m#DR*GJY5_^IIbrLgom$BPf7rSq=X#@4;(qL;2^`}OVb^;$uJnI WGV8yZX { + "egg-texture-cards -o list_item_even.egg -g -1,1,-0.5,0.5 -p 1,30 li_ready_even.png" +} + li_ready_even { + li_ready_even.png +} + { + vpool { + 0 { + -1 0.5 0 + { 0 1 } + } + 1 { + -1 -0.5 0 + { 0 0 } + } + 2 { + 1 -0.5 0 + { 1 0 } + } + 3 { + 1 0.5 0 + { 1 1 } + } + } + li_ready_even { + { + { 1 1 1 1 } + { li_ready_even } + { 0 1 2 3 { vpool } } + } + } +} diff --git a/samples/gamepad/models/list_item_odd.egg b/samples/gamepad/models/list_item_odd.egg new file mode 100644 index 0000000000..cde6c8642f --- /dev/null +++ b/samples/gamepad/models/list_item_odd.egg @@ -0,0 +1,33 @@ + { + "egg-texture-cards -o list_item_odd.egg -g -1,1,-0.5,0.5 -p 1,30 li_ready_odd.png" +} + li_ready_odd { + li_ready_odd.png +} + { + vpool { + 0 { + -1 0.5 0 + { 0 1 } + } + 1 { + -1 -0.5 0 + { 0 0 } + } + 2 { + 1 -0.5 0 + { 1 0 } + } + 3 { + 1 0.5 0 + { 1 1 } + } + } + li_ready_odd { + { + { 1 1 1 1 } + { li_ready_odd } + { 0 1 2 3 { vpool } } + } + } +} diff --git a/samples/gamepad/models/ready.png b/samples/gamepad/models/ready.png new file mode 100644 index 0000000000000000000000000000000000000000..e9cfed43da5a5c0d5429a376507d03d6daf739aa GIT binary patch literal 25376 zcmYhi1yqz>)HZw%-62Rwqmr&#Z}c)>(7b*?V7SUpww_4K)RPoEJC%0N}q@l+y$N)Tf^S3=8Awvn~*=_~{G7 zTvM2`+fkhGG=uG^sP6&*xFr9*K|p#Y#nU9F>w8ss%yld*9A<_}*Ph&`DN0v) zJy%%=J3C8zS3uU;(#+M;g5Kk+>lb>3_o^DYfdu3LKo7i^lh*cJJnk^6u!eW*FBHtxN6x%hT7mkTal^+PE$L4kTcS{ijLP3>|rI(`p zwk4bp$R61@(Cujo7UNmZV-m_Goq8v;7?Kpmk-kAUZ~nLf_21Rr{+kI=&l0())0rvj zK(=^r(r0SK9-k39cIct;+$?P1CE%0F(@DdIQa@R;{hZ@#Q!_~CWZnh#K1Co zKohX;23+N5ek1*NodT>sEyf3$8Sd#B|7Y-~wi5!K_`n({z@r%(RtKDv11H08Y}X4@ z7E_G9OCb-}yDZ~kXCqsu+r!pAJ_c-s-d}4qaUyw2b^+kR7B1?XU7T!GhPvibWp6&d zJv(z>b+2~IM*!J6h^))8X$TrNE&=BiR2q73*LSAh9Cs5q?@+FJQf(UcR8~*xA^fVu zvu|zrn#30my>d2#M0A}lwT}I|8l#r%YlKXX&ert4lXRNy>~C&@&#wM~&jwj?MkMY| zx)($d?R=2kERUiw@ES&dKL|hyVZpdiVAUX)1ORKWnM+@4a&TB{_o`c2TC$NLL48LF zHy!4=tmP1?4cxk|s2}Cw9_8`c*-P<)7qtWlZk6Kq~`0kNne{MaOFz$(w+@P ze=(&2cw=d}z39R=@gvUi=&oyloIlMM^H2rf_BW3J07OSepRcre6gxn#cLwio zkrx}|grIdgR9Gh)!;>A889yqgaF&XCz>ij2-O9x!Bm`JkSUwA6RElMON@c~rRE=-R zEIUp_PdT)TG%8$7XLM~rjMuv9=CJk4S&Q{3vdsj&nBpojf;N7b zEzE2Qi-9sF)|E4eN9N-k*&CP!)dx$oZ zCly7Z7_~zEE9#hPs5Pk#!{bjQhpmG{c~%z9#pRNNx{3e&MLvsqHieDg`kV_EtP?Gu zlK^nMy*xhFHG#NU31p-N1);jQx>~5It4Gb=_#zNns#QaoNJ#aMv$ja zwQGV(%MF?|TA2EhtUXGy5+&a6Px~?Rf6!d~v7eyrmHObgM&eyvO*8+%dAvC<>U=wo zgyh)ETz<4RD7=p@)fx(c*ulnLwlt>LXr=D5o+XaIt36{WRIfQ!+ka~X|GCIcw%6h= zLx~HYAI^HiYNIju;`av@rM0b!JUyz&(c9@3R;!2d;Bea>3_Q$8AmNqD~5A2SYnWJJTNpT(dWS6r|^%I@#`aRWVshW={EAQ?|xOfI$u zfg7i_Lcj7XMa`d-&$}C{tfg`@kLd8etA;}HXV!GrwCD8)qQUoc-UM~xY2L}d(9>|d zVDq|Pe8Ei(vhLE9{(0BmP7Q~uxSf!Bm*(9B-0nnmo;37+!&AGy&zg;qna>&iP&ISH`fOOtjfNp>)=jMiA@2T^+A>@jqp4));Ltrb z7QMKz@S&}(?Vz+c+q%lQV{y6D-{f%&aW|IGJ2(3~aM}1)9=KA-+I{ScqDt^V!bfSv zy)ClEyne>S#Eky^`O_>#g+({p%9WZ5lnP!q4ZC45=PqY4XpS!weFn zC!drEgl)fX()z{5SL- z4}U#bX1?mSL7C2uVjE*t{*Km4+KMU<@RE6g9krq#T(Dox&5w4WErY|Ky(eb0F~XX7 zQmTlAgwB6T-P%F}a=$L=be{aiJw9G)Y9hBn@xuom{Pha38n9v2D6oe@<&^32YG~`( z$zR#~ClB*~&g1BqODE>(_zqwtv4f*UaSY$%of+G%goxqBPZ8WACy;HOZI3SXDW~_^ z_ch}hwguY7r6N1?=9N6%*S7nqJ;`+5COPimUbIW@V=ZBAl}vWjQ(_A#s^=fwtBze3 zJiiLJTyUgWh1NqxT8A2-U^j)dYT zzu3gKc#4ECBkM)`MhlD4|6>WlNZyRB)cpLMgN37c0HCI&eF4Thg2U7E^Iz80)q$V% z>6X&xPmFUu44hO0>sir&FNvw&;9R9a)8Z2jvS_aG10M%16vXe(BaU^Ur^w6BtKP?d zLxY2X5)ys|4YRDdRoWEwS}-xplj7t+uk46e3X-|!7ajaznrdmO?jIJ_W~L_Y#ovN< z>Jzl-oR{z~>lQcP>V`tb^eG@tQB+mJ*6d&{Irw)- zWVO1)<8}2hWc!W3$73&fXLD0q+ZqKJMj^gup{B3rOxBU!W5I4oSbMc|u3#6h*LDwy zUDnJ^I`hCQEypiVR#X_=ZNQSojw{s{K=Nhw1i1J*iJDoDfeK%h#I*cET-#NwRm&UN zko?UHZzQ^JakT=B{jRHzR@<+O`#lhThh_?Bt;61JkD@C6|G7E@1mUBAJMyS*09-u- zmI0vI>+Eyez(0p4r}y{Ce90cEfPk#5H>1Z({%3!(ME!jpZ!UD*FOd$mqoO^quCD&6 z7|Dl8HfFppCE-wYpnpGZr^0|k{W_NR%dHo@5x*ssa0WAzKDRHLlsvoy8@O2vKg3-p zSI~M= zG$6zlqT@_)j4;VrZaMR)k~kaLZ4_O9?85Z-z6+xBJ^V}Pf3^5GU*YWP<^GHRWzBIS zr+bqoYHL1-hVF6S9UC89#v0Klfh=9REH&BRA3ssFHKA-Zx`TaJ>!K;PCvm`BMfh{> zkE~d5QBIU_0YMOu?f-Bcp5Wld1b;lsd0bqLqT)gTfSBjW!#UCqk+XbD`1rSp0PQN` zN$~%7oWHDFsP(!!>7NP;LWubxI_3x*R_}s`a~|&=Pme5|^E+Jkld{zPZ_0h>{O=rc zzEqkCWKdQ%2#bpct3wX766A>mser4u0BlNKa^-&V@c!{M#oyB3|M49zaMFe8Ky%;! zcx8ZzL_B)FUN?C{)Bhb76pW5YxC4!t*J*J-03-}XRi+r<{qtcYF!Gsierf5D`YweENCki=T3EeeP?7je zIVIa3dS2*YT-*NGFM0RZCUoPP{C_n>sq?uGe75RSP2=7MsJeg;d&wVfiyIp+sp?3- zEH!rDB=lk2R*lp>+Lm{8++zUif4f_dkCQL|J^Vj;M@lan1q@nreog0t5%#@LS!hPT z3WIHwpG@UEY@Z)DJ)S7GKmPAA7&l{|Zb#pfE;`z*%yyrH;e-8+TX+1OYCmjXT0a^B zF2Zn`O!*&OhJ`8m|GX&W`5Zo#q`A_v%8z)!0XZ&o-HCof%Kwkx6$1@KBVS|KGpqiU?IFY9>4*3 zn$30%*RSL|OuD`HB^a6tm3}KUxvFIM`&|B)t(J};4MZSsPV@FH{}G@6^446-^rco# z%fEp{0hYd`_kYvlTVl=qGkD39_A;zS(s5^spmjF(c6MEL;}W9R=f#rZJbhAAZ*v=r zSpel74UadbK0de)3zD~?Iq53Q)=%L9L73p^X~QQfp(iXX*nVDY{AjmSZ);FL&Ni|S0en)S^GW$Z;`jzXR3biOO-D?MvqkXlkbzZXK`CR z)Ab~K77w6PvtIpEt?;v`et6p_8#&RGnJn`9$XJ&PaszgmoL;gB%Ujr0_RONxR0%)t z4~SqNXYzqIF3tMdrmsC?4*HFr!m-#}g!$1so1kBF@E5Kl6qgGHE0;vDdOLRhF~fLL zYuR=?5i_pC=h#t67cC0aNzco{TYMySbzxO6?Mxo>sQ4H%K2hAnYZxnEq&d?@`~SS1l!T<)RE^=W)|$9yTS z^-868XMOwD&C&jH6rDocrn2Lzx+tMxM0$zqN>{1tDWLHFKRE$$JzJj-njWP`# zU4}p-PBDddxC(RB&1{q3*`$@knQvQD(-S%y6O{p5K<#7e?Ox_ZN!{sZz7Fopg&}>+ z!gRhMWZr0DNfY>c#DeYnq8PJ zj(O`!9}Dl*SBp-5NII(n0nbGVWoKGc4{RY5wo7`2ZqNBzfaMS6&9^7=6R45E{98bot1?~!)yB?%;wLiS>LX?>b< zj`KYqk7*2uhbKOqvI+ZV`YU?)%ZFN1die*W5Ln>q^GJ(Oh^FNa3F{;3S!N@G)-OFQ)M6cWi~4+rFiD7?{4Rf)2+cgs0X6B?kNXS*VI)0 zWam(i+kg~AUFsasl9^xZakT=ungt=ZM~&I);q08cn4!j;YDu3mk&u2(%wA8UL!0z5 z=kKZ%MK+CE4dU07N~&{NImw{!L^aE_xmsZR)+#hn zi5=on+Mt#}5eOqP8!=3O5I6M0yktYLV&8Txs^g9K&K9IcyNDR4Kl*!n;~?QH8I<6W zrvwjkmTEmQw;?~;7m=*rPaZPd?{q6XO@+D4j%8*(2KSRuOw(~GVIVsaF)Uq3q}C?p zsCKyH>dz6ifObH03G};eoWa{f897Y}F66Naif<;+VLT}N<*0oa&M=dqWM^9jymG@B zAEA+OoB-vc*`{FXm+vSLWJx`JunH?5(S2;f4i>+n0XbLgXQ<^2!S9p<)S@djy7WY7 zmtT?Jr%r(kI?uuKk0UEA6t>RMyGFN~DYKjz>Fy zqwMnORht=!a-p_9C;Uj|OWE37tvSvBvc1{B6$?HNry%qjJnO*R6Rz%5|%-@jU6VPPI4-|He= z_rtG|AGz+@w;;^_{OQ*HxhEtE+W6UT21ji{aaZ1W(my$VZ^NqJippV|b;Sy(W$e(C z@~rp8x)o!+0x!x;6_$SWu-&TCHc9N>7rxA}CY;LD#*%YmoSq?FpwTs`m8_ zcHb~FE$k_s^~$FX2twD|Kf`Zz_8AgyHEdH$b<&mjy$mf)8cG=_VB^`UJ9?y6R7zNVZ}UE=40NDo%L?=)LJbpM|wmx zUL5-FSF3Kjy>H6c92xZLYOvRgnbrAo5Ue+BVjT=TMJu#{}M!F6)d(^-C<^A{8Ma2&*fk55V(o5 zJMR_lOouA#H5u{9Em*eEB|(>4koYm4IVL%pPgRW7G?Xg9iF)UVN+@`IJ~sTNcRP!H zt7YAX^s0mYi{0rAK|E{2b-Fr(fqZ56jd2;}|bf3dSrTILtcFlT=|;3>-#sN$hclzmKo z7ZZ0hOYfZ->L1r|0Fg)h0?@Go9G0NUsINl<rY7Bhf8PlBzD?pap)V&=eYweEdXFKYeNtJ`XX za?43TCt25SpUCpR`wQD;yq_5oT7LE@*+#2AooH99vM_tpEk;CGjN~ zy*h#d(sTRO*ovKE_hK*2oI3^Zwv{Wu7*H4Qyc5N*am>d4i_u2`X{GeC29d)B%m@Ia zPqm`N9v$v_b=+i#4Q3c$xq%(k!Zf=$zkF%4i^;G5070rU)~7+nd{@WU=aSlD*b=x&SwS{I8s*$`CtTO0WijX5iab~gR1h;5Ow=k5d%mvIdwZJ1M^0B{9$Xp zORpQEH2ao4x4KE~!UJ2c33b8}dlD#n{7~ZCIY(V~y|_7d0?nnKYrXssur_FlF{3IG zUPU(h#TxnCX`3z*?fckyBL(_20qSX{B-S^vN~e~DwhB7<2Er!>QAZ>k#$jLU-n`bt zm!Kg{H>;tAhJ`!8qw-AF-52p|iEhdWE4I%y)mk0r{AQ?ODe(`gKty@7*{R2#aherG&)<=BK*F6Q2bes zHdGdj2d>XKJ&gW(0C#|Fh(^wb1|*=Fb~qKKZ}GiH1td?6Cm={zB}BL( z=05t?gIo@*sDvlg=dR<6Hdu_9CwO;oPjQ+!^gRIdXz$~DaUN$r=eK-$D^VO?y)dj8&bV0JRA&4`VTx*S=oMX&^YzyF zEy|6AcqqZ%4x()K0`78zWy<^Y!)_=8UkE<_-9Dwu9$0+ycMCD&*}spa_+DtToh~E+ zq_9ZPKO%Z%uNxT{H^$KM%6HN)c*K-#s{)=bAgBSMuC7GDv5klIGh|#j(bXk!(FAA2 zxV^84;srLd?HjoxUqC#GMMB=#+*DNlHTSFOIYg4v zF%#HTp!pbed5+PFY5R7^2rqXOUAv*~ExvTY!KJ0(E-S7o#>CZ(%X$T|LTwscb_AxqgGGAv}Pr_*Xz)Ju9u_x%!7*E>oFvaw6$~L z-ZEvkaSd*wTU|9~7+}4mtmvVZapY&TudGg2wYT(j_($}y$BpfuV*eaS3&LFz*&(W- zezKpsQHbbvkv9@k8jDL@!`Il&rEO5H&64GWS^5`U9F5dvVx*SzAAQ#Gg5PBR0zb8#F9! zki#C^urz8$ar&QL0P5UdjWrxnQTXSxB5OBohaG%8)s^=vM8dy@Yu7fRjGt4-epB5o zqr})7MdHTSZZC{`9eC?}S9iz*?V>(oxNk$aF0I?t+2Ok1f?J*ak<+8!p@@U7V~74^ zl`={3EwCy%l=o;{7UA9>Q^~ca=eYJh?fjIlW1<5loUP;C?!Pp580?uZl|;Na`$l&XDOY( znHK&LJ6h1rD`8Z$@z-ccalae?9y_@0N~Ts55;o3ZOndsRcALm4YGw*ER+;Skat*-& ze&Kcm8M6hO%GK>@$%U6*wS%Mf80I}R&&trE^jR>M$^%LweNE3hZrZ{FQkXxHHK-lRSwe1yqr+A0qRc=Ng(J^Q z51=mgrm8iSY$p^x5;K>U8ThRaa1UPrBGfGH`6Sc8x>U>6_M`2U~5f>zo5Pi{w?%~#Cez-C`T5)dFPJio$ST?+t_8E=Y=Zl>Bo^%TMiH*~kyk&^PE0Wn752 zb*rIcj}BHK=a2In6g^i`Az@(ysmv{ZH|tssxJjkh!ufVk&bN`=6auE7;=c|_5C#wL zS1l}Q;GAk%0nX#~wm7EL@uV^M=P8LZ3NLmRVV&nXYnn8PqnOr?j6{s;gFb-uH+*8^ zI49x&=aXKlPg*Z7KOC%BT1N~*8qc{|t#TG^Q25=mxCF(jBxQH=oZ+JDC>PTn1qZs; z1$oPE=3JIE&hwXuDB&G$^&;E6J2aI4H3kvUBa{bgW( z$!w4J_1!^p67Iba;k}dneoU9o06MdhgWDxnaO(Ci+LmoJWP5>Z-57%`3hV{A@UnTO zheu>qVy#{~OT6-MpAWb!lp4b?ka?m=oJ81q_x_%a>xSQw5AXTFCb3>HYWS}X2AB-N z-fPs0i-V7cXX%GqdSdO%^f@HwS#w@yk#aFp1ZV~~0~2XrNk5XB@iWOsgUSe4{dsiQ z00!~5TGjI;slgW3H|}0*o^}?QQl>|0H9THUHs5^VtvOb?CN>o@Oe0o&vw`r_m46l> z=UTtRUwapsdGQ$9a$Da>0&vln1@pw<=-GS?1#}ZB)vq$1Es4GZbmMwbonnajHXdk@ zncSaYox(!SS2w-(KFAQjZ3%sa+=d#L`#;_%5QkTj(oa(-yb}xf#1$2l9pz{LMg1ja zk$YJ$>Bm?iSh(^z5Hx*_oVK@4^L}~Ig1z3U))tCVdQSA+j$(M1fC41H3E!3 zVf>OTd+rUD`bXiun)Fl-$DPNuOnJYoi6{u9z~a}C5PK(&keOBh6nGdtuH&g%=%NX* ze}+iyyl-PQl9TH}|H=>ggdgKxDPP~m`A(JING0|-gAcjCeC=)%J==jCe+D6M4I~H1o65t=2<|j zj0NUXdjC~J>J0T*-(DH*np=SJn+xfVGwjQs#?@9S1( ze=wmo$kH`;ms$tasiI0p-iV-{pbJXpbGvd zWFChbqwTo+rC?~Mram1RqB8fMUM%^#pqgD4(p1R>lR&#-2MOuuvG4bpzJ`@ThPj*U zqMVMRgi{{o2Zo6#DU}`4=>%ISIIeUDBVg9v_lH42wMwi7xyB`dVFP-KT^h964}oVI zQaXf-oe)2dlJiNXJYLAb7cJ1eJeB0Xvo{$@X;veR(}>%C%%VSRE@XCc4quaL{S-vDG2Lw1Vm3JVm`)9PEUgM1ofWpayX{2t z=cxGzvG3fsARZ3S95*On*Lq}I5olsEZWO;~Lo?|@{U2)zJf(_5DI-QrDv&XAF*bhz zSrOElJFir9Vg5jGUh+!u-DKjIqNNB;Ak!d4Z$TK~`Y6CW9AdCW1lr2pt68f(tZo?l zlee_17n@aq5IHW1i^X#rxc!uq}M{K09gpenOM=CIckyy zOGp4d*%cVjvEDfLL#)!Bm9_f2bW!vgkfM05_-F>KRaF~!P~nBZ+{y@`zj)XlSaVZQ zOIk0^7J`@hOFh0H?ACq6yNU^fX5dNBo`9{?)ZuJ77WkNz#3rFJ}8>5a4Vh+km`aNSCY zBRVC95tSDsWkJVre6-y8U8ys>I>UMoMu!WFE@$tH^-H_U1Sf>XsSrL#h?dJQDO2~f z)@`EL?ia=o+S%1Qc#ExAfOgJ){w?&j!!qsA-{bc8eVY;8+M4dIMs@utx>w#W+xu&= zrgBL^8xi*<_+@E%{)v=zyHtaXx-Qht#a^=V5?KblZ){p*_NbV*{GM&n5L{yy%@pmc z=J+`u#qIK^4I=U~^~HD(V6}$(atAQU3H9wqOBM#l^uKe|v`Go*ej6wF(nB|<)_YTX zVdqFB7Y;gM?RUl7u5oY65?Y&2uc@& z2k_iid&JR>HqzZ$!84z#w?0PWl}+#sarWQ)&FxiDemZ+Rix>ENfXwl4PZ6UPQSURg*Y-;rp+hN_i}!yz+HNzJbku1+ z&^E`eLRwu8>IBF{+HfG_jk-;AM*_h&tGa}3qY^86jnLWt>{^nBi(0P|!<8kHR7lBJ z+t6{}PNWxzxF~y`k5l$rICV@bS9`VleTzfaPn7GoO@D72KDJe->kM}R!tdXP2^d9COUi0|BWwchhD^Cy!D_Gy)+uJ%P1X@oxgDdzISix;07f19GHtA3j>^m zlGcyY?U`)a?qv@xDW_NCu+)MM)ArHYHNn9*35!mP^}%JcdQ=9IQ8WQLRz&>qb^M=6 zhDZV5e~a$40sg5(tcF+X~r%71e9c{_L3ef5V*G0^rKvOHUWeo#nSr+n9;AcW6k)<){u}JI#8wvl(9bZIx ziyE*@cw-#-RG;S*Ac_J_YZ!YA&Q!_s`XVvVxMm|bZop@)*}BdYHwJW}%eN?Xwb~7} zD$Z;^P*!&aUb0xuY@}+e7n*u=j*d*gQ4nMp_gf^M{S(oN0wUOe@D+0;$WNtpF;RgE z_2fu+0eMfVM@reWqWOmde5`MgAXWFn7k=wias1RX@GN(XyQ9s%J}=y2U!LTt8o|hD zfhy{-?HhA|E}3&yj2kUuij7(|`*VQNvTs%P9?xLcFv}K4iWEEOa}Y4|r<$?-Fx4mR z5C^oE`t4D~!7TRwG%`Q}Umb{;VXgH)a3J}#^jUuS{Rj(Nf7R%-95Vmg8uYP@N&Mx1 z3(W&RZG&2VoQS2cNbU+B>{7R(%b-1p1*I-1aBn-y4(ATp^Ytwvi@srcw@;u*)=E>Z5BqL>C%P{B z3=V9mDN+BYrY)JkpLNPbGLjzS9DdQOs|Q}I=#PCv-=#42pokyS3?6wj2^0)eNR*2C zutl$i?g@_Bcs~yG}!2lbuqT9oqBZe8ERwzeI;ZHy@E_ z{(X?q@}Ql#Y+7}aVq=O0iay3DUb}fVUm$Z@CZrcmy_b39vBd%_$ zamlKvabQL^2IqJjmvVWX8?48TUo>jZGcj`wd8;@HJ~UHMrX-4F)I~b9VtoevYLpwt z2MxTh)s0Vf2)l7k6qn05J_**NKAM2^{)>iQw1vCTk~EKhMW(ub{8h3fXUSer$jnCh zt)O6MBT&j>=7ft+vp_%JK`=|qkou?NgFE)J4`BHIGpKA~66%3YfUo0k^5t34w{h(( zHoKuxF88>9fp%*?ErBK#VkSy`dg3dmn!g(b1(?B>&ByEv8W1bYO=hN2 zjlkOY^m!VT47qngOFROazvU&Y3xhc*$z_QnsTqlJ;uhs(YS~~fdCrNlqU|wZ zsx`Im0N(^@)oPQew?i}o@HLUqwuE4ZiCaxj$ObC}aaMq@T{_rzow5Hbxj7+DE7WvF zE5XoqX-YMmqRga3EHauk!BNhCeKNdB9Bu`Dz=MphcKw2q)QcN2{gvJQo(mM;*Ca?yo@yq3n65Okj(>Vud7njgLB7- z>J)2C$|rQiFpuT7;;c5rlBI$1hiy*hyweqg(3)t(jBCfR`@O7NEzHm{?{pvIP`mbU zced9rM~PLKI6ePJ#N)zkY2%x_iMY(A>ERG02az`Mo2VJfdYg|>TCa|9S9sedr}(K2 z6d#h%JpRs>X7T2%h26L9i@ju<``^nY3TF1=DCuwFq|L}<)@xg5+k#g*2yJm2E9tJI zy~Kdn7j0?KM`3IX^2t3u*D|@eJjH-)sq)up)lbER)LXtj6lhDor~fNc7baJ~;$FP( zpIjcCza28OH0=D_duV*Uc%31k%(tiJIE=7ycr9bHlzj2E9dTC3RiAV0*R*8eKMQI4 z$~@as(SC7oFgY{G>2tB-T}YeP+WUu#^KiMsD@W&WQAp$ut-8$QX&3_aDK^S3 z?@X<{TC|#T>OkDn3brj@_?$#TauY}nRD6DRNCMY!i) zdcX^-Z?@F2);dFVIccZ@&pSK|Zy$aUBs7fc4A&3awAhukY)YxOB79@YSFS*-gi_vH0Q|91(hI`n( zXO5Rg!%sMQ>b2-~Ts|E%`jmoMhT+8~15W79}m3E$YR0N*wYGp4j zFA*~U5t$JY*icJVovP@);cO4EdJJt>zC06H!VZGYAX;)4kZBI_1a|2C1ZIoJksOH( z69+5`v9?@0ME0y=u=}cN{Mu=+TK85F7yrvR854a9!hE}z_A9=v4a4roe6!WioE3ST zDY6_IE!aof%yVJ5Y#uX(Ouwp-2>0evRILXQ=CrZ{xzmT!;konQS}^NM@5GQZ$0HOc zQ1zeI$o&yH4iuig5y4cNV2LiIUi<0h63u+YO2D4HDKxw}+0W?YFKQrz;|r7uZ7wAo zy*{Z%2WK@qbgd3Es7DdAJDwCCF%F&?4A{tYzX@;*qcl%hrxT%7w|+wuEZqNT`-%lpEu zLITgPdi~4HB*7C`M;R1&@8kR4WA!mLiuekUtbj_gZbdQ7OGEmTW_h;r;3PE#YdnOK zMF)(_+3$K|e8*q*>>ErgnMKFX9V|g_OUIFBie?=5&Y(wVfP<<}7>5|-$oe~=Zo;4? zY+6W`w$A*#CHLE#%r8?gpRdiDITm^EiV7JBTL?N_xLupa9RDgcF80gH*xEcjfI$M7 zG{)V0B%zF<)!N>Ms^8q+-BKni%-Nu5jCB2x_%&1Du4mm*P8dJgUnQ?raYsb{0e?Rj zA!=bYVpmBVLpxMIjl`dqefIrRfoTb9e|LPWQ!!XI1Zc6%Gsd9r3VQ0FAiQteLUAAX zxRUub;tj4!)HRxm)5oYRG^M`mbi^hjDaH8fxoGRpnSNZ?8^W4q$+WF-)b{?>(WGkV zI4X475w<9b#qaV!NUSzZu1Lo$_eE?MMRU}SC#Y#m9N28uF_x1nu%NDfI2zvQ#vVu^ z@x&+m#*@e0yjV)pQa4_pY2fVT-ECDf7ggqJ@8qH9WC_i4`aYg8@1ZBx(}Q)dDC9IsY!%JSkb%kV z(=#nY^UFpUDtq!#OIgCwUrpo~8f-o^f@`4>>#NvWF1w~A*wSrl4sSaiH=Hhl5YLMps~T^LKPBTVe{`^!e^9{T4)dO<{v#_qG28r5gfPDBwJ z2|tm*IX3MRe-lvFMA{^N`+y_V_3g*>w!NXnPqo>UtCbl~6k-X!E~i1#i!ove$LawA zoy|L0oSKW=(AVTDA7ta2Yo#iUa5(>b7zQ7UCXPh4SsxR*KDv;A;NElHxtjx61=H48 zv52(Uk6|<@Z*^T)>?odi_~^5$E4PDt0&R_<4^>?5IYBDZic9+@6w1HTk2H#|TEbo| zSPKnrVTErn;>*Ihr{_PGV7KyQU%j{r@@dNVRrr=eblxHhqvX_n8Qr;R-(Ay$?lG#r zXVP-N8Y1Ffo3&X(Sz=E8#BMi9JvMeC)1p`6N99?S4)@wP(om&lo1Zt(dJQlT{Y$EU z*O(3^_>~;w_z+w0B2Qphc)JDUJbLmIdN34MSdwUZDUH)QL0?6b_k%KyVwivCgbyn2 zWMDrPq~QLPf#odITWVpKGyh=x0cTuemSl{G+CGM7x3ZB5?dM}wX!|pppt~0qd1Ro! zO0t8Hn&bp}yywPm-zrvtnrj#8oDcMx4}a4#Y6oGRB=J`uJ2T!Nw{7% z-1n%j8F@eJtM=+;_NfW;fd%5eq<`*>=)pE_nF2d=NEy^$! zIKX~FZ`RZ@PDSmEptjBsVrF z#s^aQ;f@?-(;72#YRiIfT#2ODEW%RHa*YS131Z99cUs2T6D{apyriTsxXiurz?e4f zoa@iX+MjxA#tZj`2GgTS$!#%);i4(K47kvwR>sXi&!t|4TBW}1W$wo>&D^Cbkz>G6 zxgtPGi9?6m3T5Udfdv_6Os0wQ<3~8yoTcy?{-T^0(k~Y$Jd4#Y+GNf^n`K1J6h$Ft z>X!^bb43DDM?^I-{2W>j*dQX}77CJg`rhhbytk^G3@Fzmo>MBkhrjR)*gB%#TJ?8H zoa^BHIgZE>!ctso!!tvXJJSsP9a{nuv0t-fP^7NCT(+Va>atmYOGnlG)PxOeejMj| zIW0*2o!+788_U!L1?S?%>8isGXF&DADh=Rawuhm8yfps=t-iNriGl7WvzdRKPo+gU zY{(;|s*&8TQOYnMke7T}r*vbe?+{G5>a{_&d$-z*LB)b1apF9R89SqoDj67kVwV6{eE-K6RUT zx7TSX6xWo$EVLd~1GjAWzbjJDwDVu2>kYc5()Oq`_4Tbw`*I;7%RlEGg-ir%A)L0R zGVH>5us`bJhs$?R6!G3pNYlwNeKUGe@i_aSjCNCJI%Ad#=+ICTMy~mB=i?Z9eW55S zQyU6A_(ulmCoLT-J{(3<(Th2(fN^?j_xS%VN+-pAAtVXbvS8`D;5j_g^GZ`H@o;5q zu*8C^x$xiL#EDlXtuuxvdMF|`P`*Z(79pS3E!!hO@EvJM%c>0y*2X;lUIs;CNY+-s z&F}6%@}{dC`CBSj!9*1->4Uh02l@VLuXjG# z(R&39DeP#91mUkt*D?~Ghj*7}G2*l!V!Rp;j~EMNA_DHS3PW`u^FK*lHM_&ZUcECr zhT!Ewm)lIpnB`WUvl_XC(4%BD(U;6mPz+YYIA6?hHT~%SHI3a(XVNC}` zxI(@`Ry*mFiDeJW7UJ<_y?V?jZL$(-nLYA`u;pJfQL+7sMN#GFdzA!do`%l9fso5k z3MvW^L6EfbO5WDL(eT+LC6 z+IvY-ip`IqDU&;{+e!NM;GD#JPK4D?BSOecny^6-eb)T)*&$(*YMx*H#b-(Kmvqoy znmv;c0$CN=OFcLw&*WJ$39U4fqSJ4p*D#65QnEwQA|s07c`CcbQio$ zSbN|#)dArYsS8Iibd5!rl@Rgoc`T;6Rw_tT*C}vq`R-EqI}E?D``Nc=u~}zEO@!9} zUsHDh6-C!K0DN`LGYIwU1kKtwu4I;5BG5EcPxk(5S4i3Mo{7Nr}NkVb(e zmiUJEywCf5_nbX*&hFXC8~;0V?{D%S*T$NR;&=vGkIDIJMx|uB%!l$e3An5#mJt5v+e;+*wbRz4lH- z+ocO8F9Sp zxA)}C<<-BMs4@QNhgv0w;HU{ZT)vU!x`$=Tok}|NN(Hi89PL}aEFdd5Pa>s08zAQD zPnlW#s>$=`q!gZF%$P^p*W7SVnTsI0r=x>R2ovPaeMPCH{f9)1aSTvmZp%FJ7^JYz z5bVT*Bp>31f-xxG1zMY!xqaOpP^zy1Qg;uUxLb+esQK>hTjT8YNqWf16(v+OJMTtip7@9zA;1n2M2 zW^?k2coE8}If`2H{M%jN;H{Or;9`UzLdm! z_Qt#UPuUj#DzSA_(foX2KVV4xbY^iZEhR~*fb$I&9l1ZbQ^tOfu^DEI@@db`N&gnj zdG>sBVSHI&yw1>vZNk{NS=}-cD+V)jjDdI-*^?rrylPVY0w|BuPSB|3>?GDQjhVz2 zyFI;SC&ER3y4Y}yST*+b1cgJr~Ax{v8VTMp<{m?367wP_8W zt4u!^RAE7D6A2prY}S$^X_UG<}Sb@FhCcfF_B$ z*|f*L7SybG1AS>rCwa<#!1-^+sRq#==*yP8&n9ti&pG>BLk# zgyQjHnAkce%>=Or5|5WA;ycTtmp%Ad2>dcJV>tG7ZZ6rFV2P%LLjFwN#!$LNL{s~N zi(nUVx}W#ovJrM9&@FItzDUEa;Ggy-clh+vU7>9>(y`c*X(IDP;!@tr`Q z@8-<}KUnDRinVcwguk59qECDxE~@;nzQddU56@xDcaPqT)bvz345G=3gwvB0tKy}5 zYPei1WIEJ(?h>ahIYP(A$)5}<{aN18Ih8C)YJV~eO{Ecw zo>{?%@(*)u9Juipyx+toan?f7_YgBjD>xBbzRRKNjL0`IaQuiYWu0yiGu^<oCGT=v9T4uQBmmQBs0K#{Tjciec3)taG*e$4K z{mu}}&7kTvUQZ&P+YVGvY>S%_535$8*DJF09ez;VJ3FzTHHJz2qNSx!AIF5xR&r_3 zwr<6qCHdN8qwI*jD-6|2pcN^PL@83U;lv_Fk*k*jC&oZsG112oA|)FU%McuiRdt%6 zY#h}4-7X`A&xm9~c`DPBJBM3!+yC@z+8*NBs20 zRjQQT_2AL|O_s~%7Dc8HuT*O;(^ku|;lLfR0`U1f^|mbj`Wx%($mq{#A2{a^GV;K@Xo$(tw!+u8dKs#>k`zju{E^ic_ z1;jRCxGH?(wAf6_p5&m%!h0}|ESy5UYr*@qXPuGGCqyl^ie%C|ct`31b5bjT+jHNX zUF6He`n#r-hwtl2^uJivc4ltrCuPLO=a)vNjiE>osgU!~?@_yz z3Zqrs&uqG?S=X6hzLKPG*z~7Vc3S&2DL&s<=ptomhIEj3-wcBqZPs&f9&Jx-j^MIz zz1P<_W38dIHRgf$$a+2-8aPlt<3n7F(&zuJ`I=0+i-&}BNF#ahbtosczu^kir}%Uk zT|KBjH|kGHLX?f+sT*^LC)Oh+xFfHWRX<<5PtAk-l*ymnzJU{qUf zMvpClzsz3I?U->@+`?Av_!YiFaJc3taLE4vb%7+#fX55a-+#FE%kGFQ_>oEJj;FkBnBZiNO2L`9}oh0F@l)c>3r(pM>kHj;C- zj)J**i9KL>rKMs+QHwMk2E1YSeb0yQxNX83;IV$mHnPga^1UU0dv;k?4$+?d)vRC4 z)M4C+@Q)N$dU#&12^(oS^We@!jaT2Uv?@N|ArRk=jy4@-pOka5W+D++>xz zl7k{2SZ>3bNDEwWnN*JEjIln)cd~(Fz6Si>+`?gy4kKl3)hy=h5|z~P+y3N&%h5At zsGM(*9)Dl7+%(-W<5fCfJ!%$CAv8ef`p#T5{kp@;UxP@!G_S5Rg)m9*j#I@T#l4tf zhGGU8ehsm$hO`TGbazDAFU~3ZSM{B`*j5UeU&og@O_(HejV4L3L zgVjyp=o8U@&$0U|=~aN`2$q3VJQhDTuCk~{zPqB%;9l1=UyUsr!F%xl&3+bEReK#u zF-eJ84I!9@ry*o)K?j{-hK;nBOSmMT(k7QnTSQSNb(#=8lk}~SCY4rKQLHRY=e$*DfNY+2Y z)T1JES|R|?1`mRx&p|PKoQn+SmRqNiiOm7C(Y7iOtu1N=nJJC5pd$XpkveJ5g_2=! zZfcg>d`GmmY{DEbja;BU?P6bvE0lqc;!amfBa5Wezg$ubYO|)NjyvJ1R^oKn>lL1T z>Dqx5`vhVmOg`FA2~~%t8LrlxzYLk<#S|bj1tEY#GG;k(sH>yY@sWN&Aj&erUaRqvVZE_ZF&vrNA?OoyRA?0n++%> zkJzm#d?x&5RnPmlLW`)23u{hVI`l7{s3@_P#M-Cny9>Q58C5cl{^{Y*Nj&q@v6T(&0Y7@N*+{x-Y zCq%b)=-uewbMdinZgqRTDC6cZZs=NBj$5VHoMMb#Pc)@Fo zBP0Kt^IVZDdHv;>qE?ky*zYWh{TZa6gLvvpPmIRw(-e$1W8rriBx~a0>cM6|BWu#J zreQOWyS+wA-CoH9^ZUE5#|6rADMs5am2DzQ1b>3Pa{P^Sh~)be?vLoS?ZzAXHrRde zIk9d(3thaG4(&&X=rZTxo*RGLh`N>Vjawps0{LjzOEe)~zMg+}mG2dKtvr8&4vf+s zpsBJS@$#Q@s7lwhU;MIpy{wne%dKlELSM;OQ{HV+?KxqzbLUmdJ(Bvq2z)rFZMyE# zw^-Ypf;S?J&LY{0^Ofe7x2grip6UcWF&~GlrgwY4u<1!#e9+60lO$95oZW^JuF1Ud z_0|GmY#`t&9-lFpm_ADUQEt2lni=gG;X9Wgn}2Uh<(_Go^{m!szT0}M95 zUcDz=U1qf1^EJY1DRKXPsE|l1?&NL*#l1mbZ%Bl(_C67Q^83H!sUp&FC(A&!fe?#@ z%zI<}ex_$CpYk4S-pe$#gI$)6${;+Nz2~(vmZd1Bl5@)LdTfMs=7`|T2_HU3b*~!v2iioyMH>0Ky!FFmjShI)Q6O1*=jz<=Gz^$@`*R6C9Lgri8s5-C$`tKYiAsPSq~Zd+j`sSzJ-rkAe(5vXH;%m z&^;!tbz)zbTwWJZyH4g&VA$Zn3I=)=@b$`E>%}LV4^v61Cv5>b%s+a?gWEUna^k~7 zII9jjRDF)atOb7Vs7G-XSKAOk@;e(kHnexLTBrgh$%Z$2O{464ViYyh4HWjHphKTg zjZYYM=P@OJU>S?~h7lr-{K7jHw)+(;Kc}pd`r8^F=QiY>28DZzs(cBSE^rLg!N$0H z=2ACmM!nIZ;n;QLx^x_RE(-%F3c~722i}R$)w%V@ov;XwV;$>Pgd)$4uCtLJWe+JE zOyeCJVKQwNXV<=o95yOfxnYwheM5wh7v{9qYrR7we)e2P%gs(LE zM9GBTsS%-=mB@||Ei&r2QHE*s{h6*B7em-myoWRbWB(rh&UG%*z zx9lu&`xB?^LBG;i`XsIWcMjC=kaaIV6gD8}-9Pf?VNX8_@E!57hI=_C_-3^eiAYSy zJW8Cw85uhG#Yr~g?)+XI9&2@?_uGC*N7FE@?|3qVW-R3+ca0?<|H1y%;yy#zzw!)T zYmd@mHuYG4)ct?Ij7d;_LatKQC;}vYy%Q)=GbpPG21ox**~~glmyMq0FJQo)M&L~k zwbn@RzP{r@+; zCL6G+FZvH5g+|9$WluJ=;yFv_`h?_(0d4?r077rS4zFGWIT4dEIsov?OWNHGh_FE$C|nc5r$2!mt>+3)c+ffGQd82~4dzi^uh4K}?&kn*p_Uqmwo z9!v=Vos%Ex37P)6Ml8a&>e{aXKus+q_~K8$yrh9 zCn+Tr3K}Lii=raV^_7*<2Giz_dQ7?Q@CS6^Hnxtp1@`Z&cTRzOSw$=HeD-WL4VgnDBF3$dgtJq((GsIFdSbZ0 z&LM^HaFYFM_JogqiYfdV`dN0Z0Yv zEQ$PaT#dy|&E_-pq1VpntIae{S9;_n8X0^#$hj>0*~?Kl#LN9G!+_vwRrhMO=gY-d z?>-V6xTXYFw#~24K?zNQoBbtx{cEx zuS%j4JyWAQzHSvQ%q8X24aX{PI}))4jxUh~2>4PA+NA=G(2M=0%S1IW)6V@sqhI@N zmCJhX&NZ&z5U3!ZChLcuwa@ll4)Q&3MPgc`<7=*`fdid>h9wBx6{}MKd3C<6hCI?j z9!-Fv)Qy9el$?xeA_0IK{PGebpV#rH&zIr)Ai9P=_y?i+Jmx1A($Ch_6-nKFgUO_zuJ@3rL8GkG_3-ZIS}@^zg88};H63c;ytDiRVvKLz1t%Y-SZUdrVsahcgj8gzlkH3bp*(069I0-_$ zC-`2`&;{77Cta<7f?cjh$ z7pHZ5ogr!fNHLCp)L+9a5vb}WbYXsQr;*%>u+oUaR3_J2aJ*ua>I$EKY%dsJA}c(un0<YN8g>4!y|SwGO@SqisnCWS&XbDjUn{W3Ue82plxi z)l7`aJVos8TOTW$%qv;nu`ZnuQe`ga5TeMI5L~T^I)wZpil3G5JB#YV%{-G7MGKw} z_3~45l0UhgXcWv~GR%3$+-{8*;#@h8UEoOLF#iOkfqfk@kHayI?e!2CbW)D0Eo^EayAn854xw$zjUnS;s{h1ypMhN$1 z?uBG`d4-sZ9_;v?_+l`HC^Mhcj<1{Ve;=0D2Og`ZQw7NN{SDmLNq=$8S6q`4th|gL z#E^y8pkrf!VVlf*WFkbH*dA{n*Hrl^Yv$zr+ylo_RG#44=?>c?Tz2nK zhO11^*3X9Hu~&8ZMZ>sqxlw)Q+7g*kw_$Gtn_}pVs4uNIqB9pQ`=2>q4rHlW{o?i| zPMG9m?iuS8P_*Osnl-BcSHeYAqP zXFXphMF#UdJIZ<+U*@Bni~79W>Xr9!yz@U%#+RQ<{5P!>SXJ;ERf{fPU!5dZ)-np5 z>XHEfFWv9&#adgVxiGMiUvz3!?gZl}FcT>?6IzhN4QeHB72fx0T-IXdrSl$ve4bEE z@choRDL=&0Eu_D$^M$=heNupmtlTy&)1KR+Y^r6w)>GRg^G_Va4e=Qv{g{g>c;F0) z^0%aWC`DYv?2PQ25irZp+=|RHR9boc`GFzQ$nfOTnn8{jds5u@#w5a%o*1e0h@QDg z(UGhZve)-7$XkYI#a9KBT#8^JsOCh##L^hnF-e@O~_FUGe zc?Nv++i{$Jm$tRe51%&bE)Q=>^u#ADr?SV+45&%gz78d%^$uSnztt{PYoFy-41IQ{ z7}0p%#H-E-X6q@poztWsPH|kT>7I#;SUwTlLyL7HXKLyub3taG%tnd3QPY0c*(Lg5 zy*_4n(CWLdv+TcJl?DdEDgqsUN!M|MuOB{IR)2=R4_r7k(~b=i3bc&{qZ{&bWiUGp zX*Zbz1e%>dJ<#%YnGHnaA+@nZB+i|gOkLsv)w2X)hjPLP3C6udFx;{Xxp-z7gk<|n zt6_9p*~q_^R@wRN5MMD{%U6kKMd{-BII5%t^zg8~Qabl-cenD*s|YUt^fRKCKzDXlH~4Q_ zx#2=HWW^kwzFUd_y#C$+v1FhF0!aKD@vGQy`7r{)v_J<|yjF+aYV%5@ITQ{94hVn+ zY`88~r!A1FZvMa6F{XA1Y+3PuJt!EgA4mfi`Z|i&II~f{OgJ-ULg0%l01Z_gm1-r6 Gu>S*DK+{M7 literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/thumb_click.png b/samples/gamepad/models/thumb_click.png new file mode 100644 index 0000000000000000000000000000000000000000..2fc059da828e9982bf2d5eff771f13daa6acd4c6 GIT binary patch literal 916 zcmeAS@N?(olHy`uVBq!ia0vp^3JeU4J2=>YEES8Vc|eLS$=lt9;Xep2*t>i(P=vF< zBeIx*fm;}a85w5HkpK#^mw5WRvOnP95Ytz0c-e3QC?r|p8d2h$pPQSSSHj?2l$uzQ znxasiS(2gP?&%v4-pD7;z`*p!)5S5Q;?~=1hThSE630G1xBV`(#PpHM*EG$g%~4#U zRu&dZc35>Z=iQaL^NUgDFT-7#H{vUr&px=c@WF!%3m06-vKDPob6Oz4(RAd=9@(Ji zR<7Q&SGoR|d%xRRS@(R-_dC*;&!5_~bEd$h$jvD}n;4&lynDM>;&$}bsHbyPW~xl} z`g~eZM69G&G&y^5)ZLiPI%5IBHywV|uAERZ-S+*h z+8Dq>o*E6=& zwSTuV+uND?T;krk=MMV3rBmZA~@v-<4;5j)Pk>)*IFaBbc?)k#-! z>i!v6{P-|?X4{#Zh)pKz6TW|o>scGV_10`&&tHrx<)>$UE9fc%1(Bz#pUXO@geCx2 C@=@&o literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/thumb_disabled.png b/samples/gamepad/models/thumb_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..8ad2805f10bb507d850686bed6c2afeed866a67f GIT binary patch literal 912 zcmeAS@N?(olHy`uVBq!ia0vp^3JeU4J2=>YEES8Vc|eLS$=lt9;Xep2*t>i(P=vF< zBeIx*fm;}a85w5HkpK#^mw5WRvOnP95YxAw^5RZAP)M@GHKN2hKQ}iuuY|$5C^fMp zHASI3vm`^o-P1Q9ypd0wfr06Vr;B4q#jUq@_h(Bb${hdLU-i)Gpb)1=Ws6c{0}J0u zv*@-4$-6B78}fe2TYalP5wweq>EKEiTUC^ zRo*@OCx5^5^7&Vr@3nsB$-jbCJySg`J-fTs^{MQg`o(Wj(!mGYmaKZW?VHNmh=XTV zPkLe@{nAM>zsFNG_qJEd+#5Ob%B6af&zEM`P5610Yg%mI=85YgaObbRVtA_3~AL_kUFVytXE$5WKxBcOo-4D0z73TlVoWA#3S5nv?g+ys4`Z}HAz+qM$ z9UZkre0%17ls1s)IX3f5mQj|2+U8j@JZ_IyH|$fXyyv<-BE~$B85nPmE5+|V*f4*> zmzYbFPac_DY{rOWW37_`UF^&3hB6YEES8Vc|eLS$=lt9;Xep2*t>i(P=vF< zBeIx*fm;}a85w5HkpK#^mw5WRvOnP95Ysnd2|p+X6p}1)jVN)>&&^HED`9XhN=+EaktaqI2fjo#6LBF8`8->GlH=VoNN=fQ(8CRX=$ zo~8?$N6wzCcd#leczI)~vwPvo7gKyWM7UVl+xcXfSWiB?v`2CB53%SIwzBgUIk8UO zw_J2zy4R-yStcd&Tj(U8Nl|OB<#8lN z_eU5W`&VIOx532Me6mWVx@TJcw54mWATgP zob_-2mL6pt8d*ce)`Vm z->>rAE(imIht6Qd+0V-+PoCVnfA77QC4KU&2L1t>4ZqX==^qFv`>`fD+kfL5nZwUZ zT$PmEE$fAE_~(@7tnG^Y*xZuLL1(u!oR{1%B}Bf` z{IS@B>mj!?LilfFh(wwM{an^LB{Ts5LDxGM literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/thumb_map.egg b/samples/gamepad/models/thumb_map.egg new file mode 100644 index 0000000000..1b49062ef5 --- /dev/null +++ b/samples/gamepad/models/thumb_map.egg @@ -0,0 +1,117 @@ + { + "egg-texture-cards -o thumb_map.egg -p 32,440 -g -0.04,0.04,-0.55,0.55 -wm clamp thumb_ready.png thumb_click.png thumb_hover.png thumb_disabled.png" +} + thumb_ready { + thumb_ready.png + wrap { clamp } +} + thumb_hover { + thumb_hover.png + wrap { clamp } +} + thumb_disabled { + thumb_disabled.png + wrap { clamp } +} + thumb_click { + thumb_click.png + wrap { clamp } +} + { + { 1 } + fps { 2 } + vpool { + 0 { + -0.04 0.55 0 + { 0 1 } + } + 1 { + -0.04 -0.55 0 + { 0 0 } + } + 2 { + 0.04 -0.55 0 + { 1 0 } + } + 3 { + 0.04 0.55 0 + { 1 1 } + } + 4 { + -0.04 0.55 0 + { 0 1 } + } + 5 { + -0.04 -0.55 0 + { 0 0 } + } + 6 { + 0.04 -0.55 0 + { 1 0 } + } + 7 { + 0.04 0.55 0 + { 1 1 } + } + 8 { + -0.04 0.55 0 + { 0 1 } + } + 9 { + -0.04 -0.55 0 + { 0 0 } + } + 10 { + 0.04 -0.55 0 + { 1 0 } + } + 11 { + 0.04 0.55 0 + { 1 1 } + } + 12 { + -0.04 0.55 0 + { 0 1 } + } + 13 { + -0.04 -0.55 0 + { 0 0 } + } + 14 { + 0.04 -0.55 0 + { 1 0 } + } + 15 { + 0.04 0.55 0 + { 1 1 } + } + } + thumb_ready { + { + { 1 1 1 1 } + { thumb_ready } + { 0 1 2 3 { vpool } } + } + } + thumb_click { + { + { 1 1 1 1 } + { thumb_click } + { 4 5 6 7 { vpool } } + } + } + thumb_hover { + { + { 1 1 1 1 } + { thumb_hover } + { 8 9 10 11 { vpool } } + } + } + thumb_disabled { + { + { 1 1 1 1 } + { thumb_disabled } + { 12 13 14 15 { vpool } } + } + } +} diff --git a/samples/gamepad/models/thumb_ready.png b/samples/gamepad/models/thumb_ready.png new file mode 100644 index 0000000000000000000000000000000000000000..36a41ae5995a216062367d7eba0ab5752a9cfb4f GIT binary patch literal 815 zcmeAS@N?(olHy`uVBq!ia0vp^3JeU4J2=>YEES8Vc|eLS$=lt9;Xep2*t>i(P=vF< zBeIx*fm;}a85w5HkpK#^mw5WRvOnP95Yv~s(Rk)2P)M@GHKN2hKQ}iuuY|$5C^fMp zHASI3vm`^o-P1Q9ypd0wfq`kdr;B4q#jUq@_h$DaZ5=<^RfI~v7iRZN$vsDId4 zP=50HSDWv(dN(H}2bCr;uVB(n3D13C`aw>?+-e7J&c++J*vpO?+jA_w*|>vWJ$-`8 z%l;kmM#n0za4$|}pOsp%lP|ye!yDx-Y#-j*?B&d#wjhvGl3{(-0XM!1@pqLq@>TmE zoP3@W{_m{Sl>9@7%?x_fc+1yw>~6n#|JU8a6Ic&;nOGKT%TKd?oyF@Ewj$$K%B=5y zHqAQMvE!MLfP=?uMI-x}OhQy2}-F!MK97d$q%iEVb@ z{_wY^%-logRrWqOE3{_^cmL@Rf4GkvKcL=VRUqKDnYmEj@0^0?CHX@8l%CHkp1bYI zO~^hdogjZhviu4A1MLl5Q)YagKW|+`ZbClG1O5-wFLpJ(cs&!Cju|{%{an^LB{Ts5 Dcsm*= literal 0 HcmV?d00001 From 3227fe2ebd367560b06e9542b6f295a18c83ad02 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 14:02:37 +0200 Subject: [PATCH 29/82] Fixed and enabled logging for some functions Added missing include uncomment logging in find- and get_control as well as find- and get_button functions --- panda/src/device/inputDevice.I | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index ab30e9eb49..b09a89c7b1 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -11,6 +11,8 @@ * @date 2015-12-11 */ +#include "config_device.h" + /** * */ @@ -249,8 +251,8 @@ get_button(size_t index) const { if (index >= 0 && index < (int)_buttons.size()) { return _buttons[index]; } else { - /*device_cat.error() - << "Index " << index<< " was not found in the controls list\n";*/ + device_cat.error() + << "Index " << index<< " was not found in the controls list\n"; nassertr(false, ButtonState()); } } @@ -266,8 +268,8 @@ find_button(ButtonHandle handle) const { return _buttons[i]; } } - /*device_cat.error() - << "Axis " << axis << " was not found in the controls list\n";*/ + device_cat.error() + << "Handle " << handle.get_name() << " was not found in the controls list\n"; nassertr(false, ButtonState()); } @@ -335,8 +337,8 @@ get_control(size_t index) const { if (index >= 0 && index < (int)_controls.size()) { return _controls[index]; } else { - /*device_cat.error() - << "Index " << index<< " was not found in the controls list\n";*/ + device_cat.error() + << "Index " << index<< " was not found in the controls list\n"; nassertr(false, AnalogState()); } } @@ -352,8 +354,8 @@ find_control(ControlAxis axis) const { return _controls[i]; } } - /*device_cat.error() - << "Axis " << axis << " was not found in the controls list\n";*/ + device_cat.error() + << "Axis " << axis << " was not found in the controls list\n"; nassertr(false, AnalogState()); } From f1444fcff966069f82d86a4d7edfdefaf61f2c1a Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 16:44:09 +0200 Subject: [PATCH 30/82] Fixed << operator to be usable in .I file --- panda/src/device/inputDevice.I | 4 ++-- panda/src/device/inputDevice.h | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index b09a89c7b1..a6094c8c69 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -290,7 +290,7 @@ get_num_controls() const { * the various controls by index number. */ INLINE void InputDevice:: -set_control_map(int index, ControlAxis axis) { +set_control_map(int index, InputDevice::ControlAxis axis) { LightMutexHolder holder(_lock); nassertv(index >= 0); if (index >= (int)_controls.size()) { @@ -348,7 +348,7 @@ get_control(size_t index) const { * if the axis was not found in the list. */ INLINE InputDevice::AnalogState InputDevice:: -find_control(ControlAxis axis) const { +find_control(InputDevice::ControlAxis axis) const { for (int i; i < (int)_controls.size(); i++) { if (_controls[i].axis == axis) { return _controls[i]; diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index f036394b92..a007fbb081 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -287,7 +287,8 @@ PUBLISHED: INLINE AnalogState get_control(size_t index) const; INLINE AnalogState find_control(ControlAxis axis) const; - // Make device controls iterable + // Make device buttons and controls iterable + MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button); MAKE_SEQ_PROPERTY(controls, get_num_controls, get_control); public: @@ -313,8 +314,8 @@ INLINE ostream &operator << (ostream &out, const InputDevice &device) { return out; } -ostream &operator << (ostream &out, InputDevice::DeviceClass dc); -ostream &operator << (ostream &out, InputDevice::ControlAxis axis); +EXPCL_PANDA_DEVICE ostream &operator << (ostream &out, InputDevice::DeviceClass dc); +EXPCL_PANDA_DEVICE ostream &operator << (ostream &out, InputDevice::ControlAxis axis); #include "inputDevice.I" From 719e77a191f49c6accb777cd0b0a3465355762ff Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 16:46:20 +0200 Subject: [PATCH 31/82] Changed button mapping changed mapping of previous and next (i.e. wiimote's + and - keys) to the same as the select and start button --- panda/src/device/evdevInputDevice.cxx | 8 ++------ panda/src/device/linuxJoystickDevice.cxx | 10 ++-------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 573042c5d2..3edde84935 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -768,20 +768,16 @@ map_button(int code) { return GamepadButton::action_2(); case BTN_SELECT: + case KEY_PREVIOUS: return GamepadButton::back(); case BTN_START: + case KEY_NEXT: return GamepadButton::start(); case BTN_MODE: return GamepadButton::guide(); - case KEY_NEXT: - return GamepadButton::next(); - - case KEY_PREVIOUS: - return GamepadButton::previous(); - case BTN_THUMBL: return GamepadButton::lstick(); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index a1abddf3dd..fc2e2ba7b1 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -172,10 +172,12 @@ open_device() { break; case BTN_SELECT: + case KEY_PREVIOUS: handle = GamepadButton::back(); break; case BTN_START: + case KEY_NEXT: handle = GamepadButton::start(); break; @@ -183,14 +185,6 @@ open_device() { handle = GamepadButton::guide(); break; - case KEY_NEXT: - handle = GamepadButton::next(); - break; - - case KEY_PREVIOUS: - handle = GamepadButton::previous(); - break; - case BTN_THUMBL: handle = GamepadButton::lstick(); break; From f2a3a451005710a6722d5bffda38289086ccf279 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 16:46:39 +0200 Subject: [PATCH 32/82] added assert to debugging output --- direct/src/showbase/ShowBase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index b1e84d1aae..1aeddf4f66 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -1724,7 +1724,7 @@ class ShowBase(DirectObject.DirectObject): id+=1 # Setup the button thrower for that device and register it's event prefix bt = idn.attachNewNode(ButtonThrower(prefix)) - self.notify.debug("Registred event prefix {}{}-".format(prefix, id)) + assert self.notify.debug("Registered event prefix {}{}-".format(prefix, id)) bt.node().setPrefix("{}{}-".format(prefix, id)) # append the new button thrower to the list of device button throwers self.deviceButtonThrowers.append(bt) From dc1ec6b87747e221a19ab4ae42090ba84e51616d Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 16:48:53 +0200 Subject: [PATCH 33/82] small cleanup of sample files Further documentation simplyfy lines by separately storing the value of findControl --- samples/gamepad/gamepad.py | 17 ++++++++++++----- samples/gamepad/steeringWheel.py | 8 +++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/samples/gamepad/gamepad.py b/samples/gamepad/gamepad.py index fe6cbd296f..82154838f6 100644 --- a/samples/gamepad/gamepad.py +++ b/samples/gamepad/gamepad.py @@ -56,16 +56,19 @@ class App(ShowBase): # movements by ourself self.disableMouse() + # list of connected gamepad devices + gamepads = base.devices.getDevices(InputDevice.DC_gamepad) + # set the center position of the control sticks # NOTE: here we assume, that the wheel is centered when the application get started. # In real world applications, you should notice the user and give him enough time # to center the wheel until you store the center position of the controler! - gamepads = base.devices.getDevices(InputDevice.DC_gamepad) self.lxcenter = gamepads[0].findControl(InputDevice.C_left_x).state self.lycenter = gamepads[0].findControl(InputDevice.C_left_y).state self.rxcenter = gamepads[0].findControl(InputDevice.C_right_x).state self.rycenter = gamepads[0].findControl(InputDevice.C_right_y).state + self.taskMgr.add(self.moveTask, "movement update task") def connect(self, device): @@ -106,13 +109,17 @@ class App(ShowBase): # we will use the first found gamepad # Move the camera left/right - movementVec.setX(gamepads[0].findControl(InputDevice.C_left_x).state - self.lxcenter) + left_x = gamepads[0].findControl(InputDevice.C_left_x) + movementVec.setX(left_x.state - self.lxcenter) # Move the camera forward/backward - movementVec.setY(gamepads[0].findControl(InputDevice.C_left_y).state - self.lycenter) + left_y = gamepads[0].findControl(InputDevice.C_left_y) + movementVec.setY(left_y.state - self.lycenter) # Control the cameras heading - base.camera.setH(base.camera, 100 * dt * (gamepads[0].findControl(InputDevice.C_right_x).state - self.rxcenter)) + right_x = gamepads[0].findControl(InputDevice.C_right_x) + base.camera.setH(base.camera, 100 * dt * (right_x.state - self.rxcenter)) # Control the cameras pitch - base.camera.setP(base.camera, 100 * dt * (gamepads[0].findControl(InputDevice.C_right_y).state - self.rycenter)) + right_y = gamepads[0].findControl(InputDevice.C_right_y) + base.camera.setP(base.camera, 100 * dt * (right_y.state - self.rycenter)) # calculate movement base.camera.setX(base.camera, 100 * dt * movementVec.getX()) diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py index 256c2e1eb3..306419c378 100644 --- a/samples/gamepad/steeringWheel.py +++ b/samples/gamepad/steeringWheel.py @@ -114,13 +114,15 @@ class App(ShowBase): # we will use the first found wheel # Acclerate - accleration = wheels[0].findControl(InputDevice.C_accelerator).state * self.maxAccleration - if self.currentMoveSpeed > wheels[0].findControl(InputDevice.C_accelerator).state * self.maxSpeed: + acclearatorPedal = wheels[0].findControl(InputDevice.C_accelerator).state + accleration = accleratorPedal * self.maxAccleration + if self.currentMoveSpeed > accleratorPedal * self.maxSpeed: self.currentMoveSpeed -= dt * self.deaccleration self.currentMoveSpeed += dt * accleration # Break - deacleration = wheels[0].findControl(InputDevice.C_brake).state * self.deaclerationBreak + breakPedal = wheels[0].findControl(InputDevice.C_brake).state + deacleration = breakPedal * self.deaclerationBreak self.currentMoveSpeed -= dt * deacleration if self.currentMoveSpeed < 0: self.currentMoveSpeed = 0 From 2da890800fe01eb0bd019eeed76c986b739a835b Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 11 Aug 2016 12:05:07 +0200 Subject: [PATCH 34/82] Fix hat mapping if dpad emulation is deactivated --- panda/src/device/evdevInputDevice.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 3edde84935..39ba47b236 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -386,6 +386,10 @@ init_device() { _dpad_up_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } else if (i == ABS_HAT0X) { + set_control_map(i, C_hat_x); + } else if (i == ABS_HAT0Y) { + set_control_map(i, C_hat_y); } } else { set_control_map(i, axis_map[i]); From fa7f385bb8ab3fe71cb579d42635622fd453de47 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 28 Dec 2016 17:20:20 +0100 Subject: [PATCH 35/82] More work on integrating new input framework --- panda/src/device/evdevInputDevice.cxx | 15 ++++- panda/src/device/inputDevice.I | 56 +++++++++---------- panda/src/device/inputDevice.cxx | 36 +++++++++++- panda/src/device/inputDevice.h | 20 +++---- panda/src/display/graphicsWindow.cxx | 5 -- panda/src/display/graphicsWindowInputDevice.I | 12 +--- .../src/display/graphicsWindowInputDevice.cxx | 4 +- panda/src/display/graphicsWindowInputDevice.h | 5 -- panda/src/egldisplay/eglGraphicsWindow.cxx | 38 ------------- panda/src/egldisplay/eglGraphicsWindow.h | 1 - panda/src/putil/gamepadButton.cxx | 4 ++ panda/src/putil/gamepadButton.h | 2 + panda/src/x11display/x11GraphicsWindow.cxx | 14 ++--- panda/src/x11display/x11GraphicsWindow.h | 7 --- 14 files changed, 97 insertions(+), 122 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 39ba47b236..7a71e5f222 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -346,8 +346,16 @@ init_device() { int bi = 0; for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { - set_button_map(bi, map_button(i)); - //cerr << "Button " << bi << " is mapped by the driver to " << i << "\n"; + ButtonHandle mapped = map_button(i); + set_button_map(bi, mapped); + + if (mapped == ButtonHandle::none()) { + if (device_cat.is_debug()) { + device_cat.debug() << "Unmapped /dev/input/event" << _index + << " button " << bi << ": 0x" << hex << i << dec << "\n"; + } + } + if (test_bit(i, states)) { _buttons[bi].state = S_down; all_values_zero = false; @@ -735,6 +743,9 @@ map_button(int code) { } switch (code) { + case BTN_TRIGGER: + return GamepadButton::trigger(); + case BTN_A: return GamepadButton::action_a(); diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index a6094c8c69..b512b973a8 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -175,7 +175,7 @@ get_max_battery_level() const { * associated with a ButtonHandle even if their state is unknown. This number * may change as more buttons are discovered. */ -INLINE int InputDevice:: +INLINE size_t InputDevice:: get_num_buttons() const { LightMutexHolder holder(_lock); return _buttons.size(); @@ -192,10 +192,9 @@ get_num_buttons() const { * generate ButtonEvents when the buttons change state. */ INLINE void InputDevice:: -set_button_map(int index, ButtonHandle button) { +set_button_map(size_t index, ButtonHandle button) { LightMutexHolder holder(_lock); - nassertv(index >= 0); - if (index >= (int)_buttons.size()) { + if (index >= _buttons.size()) { _buttons.resize(index + 1, ButtonState()); } @@ -208,8 +207,8 @@ set_button_map(int index, ButtonHandle button) { * was associated. */ INLINE ButtonHandle InputDevice:: -get_button_map(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { +get_button_map(size_t index) const { + if (index < _buttons.size()) { return _buttons[index].handle; } else { return ButtonHandle::none(); @@ -221,8 +220,8 @@ get_button_map(int index) const { * currently known to be down, or false if it is up or unknown. */ INLINE bool InputDevice:: -get_button_state(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { +get_button_state(size_t index) const { + if (index < _buttons.size()) { return (_buttons[index].state == S_down); } else { return false; @@ -234,8 +233,8 @@ get_button_state(int index) const { * have never heard anything about this particular button. */ INLINE bool InputDevice:: -is_button_known(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { +is_button_known(size_t index) const { + if (index < _buttons.size()) { return _buttons[index].state != S_unknown; } else { return false; @@ -248,12 +247,12 @@ is_button_known(int index) const { */ INLINE InputDevice::ButtonState InputDevice:: get_button(size_t index) const { - if (index >= 0 && index < (int)_buttons.size()) { + if (index < _buttons.size()) { return _buttons[index]; } else { device_cat.error() - << "Index " << index<< " was not found in the controls list\n"; - nassertr(false, ButtonState()); + << "Index " << index << " was not found in the controls list\n"; + return ButtonState(); } } @@ -263,21 +262,21 @@ get_button(size_t index) const { */ INLINE InputDevice::ButtonState InputDevice:: find_button(ButtonHandle handle) const { - for (int i; i < (int)_buttons.size(); i++) { + for (size_t i = 0; i < _buttons.size(); ++i) { if (_buttons[i].handle == handle) { return _buttons[i]; } } device_cat.error() << "Handle " << handle.get_name() << " was not found in the controls list\n"; - nassertr(false, ButtonState()); + return ButtonState(); } /** * Returns the number of analog controls known to the InputDevice. This number * may change as more controls are discovered. */ -INLINE int InputDevice:: +INLINE size_t InputDevice:: get_num_controls() const { return _controls.size(); } @@ -290,10 +289,9 @@ get_num_controls() const { * the various controls by index number. */ INLINE void InputDevice:: -set_control_map(int index, InputDevice::ControlAxis axis) { +set_control_map(size_t index, InputDevice::ControlAxis axis) { LightMutexHolder holder(_lock); - nassertv(index >= 0); - if (index >= (int)_controls.size()) { + if (index >= _controls.size()) { _controls.resize(index + 1, AnalogState()); } @@ -306,8 +304,8 @@ set_control_map(int index, InputDevice::ControlAxis axis) { * associated. */ INLINE InputDevice::ControlAxis InputDevice:: -get_control_map(int index) const { - if (index >= 0 && index < (int)_controls.size()) { +get_control_map(size_t index) const { + if (index < _controls.size()) { return _controls[index].axis; } else { return C_none; @@ -320,8 +318,8 @@ get_control_map(int index) const { * single control is -1.0 to 1.0. */ INLINE double InputDevice:: -get_control_state(int index) const { - if (index >= 0 && index < (int)_controls.size()) { +get_control_state(size_t index) const { + if (index < _controls.size()) { return _controls[index].state; } else { return 0.0; @@ -334,12 +332,12 @@ get_control_state(int index) const { */ INLINE InputDevice::AnalogState InputDevice:: get_control(size_t index) const { - if (index >= 0 && index < (int)_controls.size()) { + if (index < _controls.size()) { return _controls[index]; } else { device_cat.error() << "Index " << index<< " was not found in the controls list\n"; - nassertr(false, AnalogState()); + return AnalogState(); } } @@ -349,14 +347,14 @@ get_control(size_t index) const { */ INLINE InputDevice::AnalogState InputDevice:: find_control(InputDevice::ControlAxis axis) const { - for (int i; i < (int)_controls.size(); i++) { + for (size_t i = 0; i < _controls.size(); ++i) { if (_controls[i].axis == axis) { return _controls[i]; } } device_cat.error() << "Axis " << axis << " was not found in the controls list\n"; - nassertr(false, AnalogState()); + return AnalogState(); } /** @@ -364,8 +362,8 @@ find_control(InputDevice::ControlAxis axis) const { * if we have never heard anything about this particular control. */ INLINE bool InputDevice:: -is_control_known(int index) const { - if (index >= 0 && index < (int)_controls.size()) { +is_control_known(size_t index) const { + if (index < _controls.size()) { return _controls[index].known; } else { return false; diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 956a821bef..6a910de566 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -212,8 +212,8 @@ set_control_state(int index, double state) { device_cat.spam() << "Changed control " << index; - if (_controls[index].known != C_none) { - device_cat.spam(false) << " (" << _controls[index].known << ")"; + if (_controls[index].axis != C_none) { + device_cat.spam(false) << " (" << _controls[index].axis << ")"; } device_cat.spam(false) << " to " << state << "\n"; @@ -438,6 +438,14 @@ operator << (ostream &out, InputDevice::DeviceClass dc) { case InputDevice::DC_steering_wheel: out << "steering_wheel"; break; + + case InputDevice::DC_dance_pad: + out << "dance_pad"; + break; + + case InputDevice::DC_hmd: + out << "hmd"; + break; } return out; } @@ -488,6 +496,30 @@ operator << (ostream &out, InputDevice::ControlAxis axis) { case InputDevice::C_throttle: out << "throttle"; break; + + case InputDevice::C_rudder: + out << "rudder"; + break; + + case InputDevice::C_hat_x: + out << "hat_x"; + break; + + case InputDevice::C_hat_y: + out << "hat_y"; + break; + + case InputDevice::C_wheel: + out << "wheel"; + break; + + case InputDevice::C_accelerator: + out << "accelerator"; + break; + + case InputDevice::C_brake: + out << "brake"; + break; } return out; diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index a007fbb081..d964b80781 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -156,17 +156,17 @@ PUBLISHED: INLINE short get_battery_level() const; INLINE short get_max_battery_level() const; - INLINE int get_num_buttons() const; - INLINE void set_button_map(int index, ButtonHandle button); - INLINE ButtonHandle get_button_map(int index) const; - INLINE bool get_button_state(int index) const; - INLINE bool is_button_known(int index) const; + INLINE size_t get_num_buttons() const; + INLINE void set_button_map(size_t index, ButtonHandle button); + INLINE ButtonHandle get_button_map(size_t index) const; + INLINE bool get_button_state(size_t index) const; + INLINE bool is_button_known(size_t index) const; - INLINE int get_num_controls() const; - INLINE void set_control_map(int index, ControlAxis axis); - INLINE ControlAxis get_control_map(int index) const; - INLINE double get_control_state(int index) const; - INLINE bool is_control_known(int index) const; + INLINE size_t get_num_controls() const; + INLINE void set_control_map(size_t index, ControlAxis axis); + INLINE ControlAxis get_control_map(size_t index) const; + INLINE double get_control_state(size_t index) const; + INLINE bool is_control_known(size_t index) const; INLINE void set_vibration(double strong, double weak); diff --git a/panda/src/display/graphicsWindow.cxx b/panda/src/display/graphicsWindow.cxx index cb7960313e..d37d765d91 100644 --- a/panda/src/display/graphicsWindow.cxx +++ b/panda/src/display/graphicsWindow.cxx @@ -690,11 +690,6 @@ add_input_device(InputDevice *device) { LightMutexHolder holder(_input_lock); int index = (int)_input_devices.size(); _input_devices.push_back(device); - - if (device->is_of_type(GraphicsWindowInputDevice::get_class_type())) { - ((GraphicsWindowInputDevice *)device)->set_device_index(index); - } - return index; } diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index 9d89dc308e..ef97c1f974 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -15,17 +15,7 @@ * */ INLINE GraphicsWindowInputDevice:: -GraphicsWindowInputDevice() : _host(NULL) { -} - -/** - * Set the device index. This is reported in pointer events. The device - * index will be equal to the position of the GraphicsWindowInputDevice in the - * window's list. - */ -INLINE void GraphicsWindowInputDevice:: -set_device_index(int index) { - _device_index = index; +GraphicsWindowInputDevice() { } /** diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index 4f30561112..6e0a5a7dfe 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -28,8 +28,7 @@ TypeHandle GraphicsWindowInputDevice::_type_handle; */ GraphicsWindowInputDevice:: GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : - InputDevice(name, DC_virtual, flags), - _host(host) + InputDevice(name, DC_virtual, flags) { } @@ -74,7 +73,6 @@ GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) { */ void GraphicsWindowInputDevice:: operator = (const GraphicsWindowInputDevice ©) { - _host = copy._host; InputDevice::operator = (copy); } diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index 50bd03e01f..b5b6a98a46 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -39,8 +39,6 @@ public: void operator = (const GraphicsWindowInputDevice ©); ~GraphicsWindowInputDevice(); - INLINE void set_device_index(int index); - PUBLISHED: // The following interface is for the various kinds of GraphicsWindows to // record the data incoming on the device. @@ -61,9 +59,6 @@ PUBLISHED: INLINE void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); private: - GraphicsWindow *_host; - int _device_index; - typedef pset ButtonsHeld; ButtonsHeld _buttons_held; diff --git a/panda/src/egldisplay/eglGraphicsWindow.cxx b/panda/src/egldisplay/eglGraphicsWindow.cxx index b7e7d5dccf..b9118a3f93 100644 --- a/panda/src/egldisplay/eglGraphicsWindow.cxx +++ b/panda/src/egldisplay/eglGraphicsWindow.cxx @@ -55,44 +55,6 @@ eglGraphicsWindow:: ~eglGraphicsWindow() { } -/** - * Forces the pointer to the indicated position within the window, if - * possible. - * - * Returns true if successful, false on failure. This may fail if the mouse - * is not currently within the window, or if the API doesn't support this - * operation. - */ -bool eglGraphicsWindow:: -move_pointer(int device, int x, int y) { - // Note: this is not thread-safe; it should be called only from App. - // Probably not an issue. - if (device == 0) { - // Move the system mouse pointer. - if (!_properties.get_foreground() || - !_input_devices[0].get_pointer().get_in_window()) { - // If the window doesn't have input focus, or the mouse isn't currently - // within the window, forget it. - return false; - } - - const MouseData &md = _input_devices[0].get_pointer(); - if (!md.get_in_window() || md.get_x() != x || md.get_y() != y) { - XWarpPointer(_display, None, _xwindow, 0, 0, 0, 0, x, y); - _input_devices[0].set_pointer_in_window(x, y); - } - return true; - } else { - // Move a raw mouse. - if ((device < 1)||(device >= _input_devices.size())) { - return false; - } - _input_devices[device].set_pointer_in_window(x, y); - return true; - } -} - - /** * This function will be called within the draw thread before beginning * rendering for a given frame. It should do whatever setup is required, and diff --git a/panda/src/egldisplay/eglGraphicsWindow.h b/panda/src/egldisplay/eglGraphicsWindow.h index 508365cc65..e936a97cef 100644 --- a/panda/src/egldisplay/eglGraphicsWindow.h +++ b/panda/src/egldisplay/eglGraphicsWindow.h @@ -33,7 +33,6 @@ public: GraphicsOutput *host); virtual ~eglGraphicsWindow(); - virtual bool move_pointer(int device, int x, int y); virtual bool begin_frame(FrameMode mode, Thread *current_thread); virtual void end_frame(FrameMode mode, Thread *current_thread); virtual void end_flip(); diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index ba7fe41b5a..7ddbc33b17 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -47,6 +47,8 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(action_z) DEFINE_GAMEPAD_BUTTON_HANDLE(action_1) DEFINE_GAMEPAD_BUTTON_HANDLE(action_2) +DEFINE_GAMEPAD_BUTTON_HANDLE(trigger) + /** * This is intended to be called only once, by the static initialization * performed in config_util.cxx. @@ -81,4 +83,6 @@ init_gamepad_buttons() { ButtonRegistry::ptr()->register_button(_action_1, "action_1"); ButtonRegistry::ptr()->register_button(_action_2, "action_2"); + + ButtonRegistry::ptr()->register_button(_trigger, "trigger"); } diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index 9a8597a6b9..0edbbad99b 100644 --- a/panda/src/putil/gamepadButton.h +++ b/panda/src/putil/gamepadButton.h @@ -53,6 +53,8 @@ PUBLISHED: static ButtonHandle action_1(); static ButtonHandle action_2(); + static ButtonHandle trigger(); + public: static void init_gamepad_buttons(); }; diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 43cff8e061..949d4841d0 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -131,14 +131,13 @@ move_pointer(int device, int x, int y) { // Probably not an issue. if (device == 0) { // Move the system mouse pointer. - if (!_properties.get_foreground() || - !_input->get_pointer().get_in_window()) { + PointerData md = _input->get_pointer(); + if (!_properties.get_foreground() || !md.get_in_window()) { // If the window doesn't have input focus, or the mouse isn't currently // within the window, forget it. return false; } - const MouseData &md = _input->get_pointer(); if (!md.get_in_window() || md.get_x() != x || md.get_y() != y) { if (!_dga_mouse_enabled) { XWarpPointer(_display, None, _xwindow, 0, 0, 0, 0, x, y); @@ -147,12 +146,8 @@ move_pointer(int device, int x, int y) { } return true; } else { - // Move a raw mouse. - if (device < 1 || device >= _input_devices.size()) { - return false; - } - //_input_devices[device]->set_pointer_in_window(x, y); - return true; + // Can't move a raw mouse. + return false; } } @@ -1236,6 +1231,7 @@ setup_colormap(XVisualInfo *visual) { /** * Adds raw mice to the _input_devices list. + * @deprecated obtain raw devices via the device manager instead. */ void x11GraphicsWindow:: open_raw_mice() { diff --git a/panda/src/x11display/x11GraphicsWindow.h b/panda/src/x11display/x11GraphicsWindow.h index 1400456d3b..c452622d03 100644 --- a/panda/src/x11display/x11GraphicsWindow.h +++ b/panda/src/x11display/x11GraphicsWindow.h @@ -102,13 +102,6 @@ protected: Bool _override_redirect; Atom _wm_delete_window; - struct MouseDeviceInfo { - int _fd; - int _input_device_index; - string _io_buffer; - }; - pvector _mouse_device_info; - public: static TypeHandle get_class_type() { return _type_handle; From f1e1c2b891fbfb19ae7e4a277390c4db21018206 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 8 Feb 2017 00:16:06 +0100 Subject: [PATCH 36/82] Fixes for device button handling Made State enum accessible from python side Fixed missing state changing in evdevInputDevice class --- panda/src/device/evdevInputDevice.cxx | 15 +++++++++++++++ panda/src/device/inputDevice.h | 3 +-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 7a71e5f222..ca2247c012 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -574,6 +574,21 @@ process_events() { case EV_KEY: button = map_button(code); _button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time)); + // set the buttons state + State state; + if (events[i].value) { + state = S_down; + } else { + state = S_up; + } + for (int i = 0; i < _buttons.size(); ++i) { + if (get_button(i).handle == button) { + //NOTE: set_button_state doesn't work correct here. + //set_button_state(i, state); + _buttons[i].state = state; + break; + } + } break; default: diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index d964b80781..0850d4a1d3 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -243,14 +243,13 @@ protected: PT(ButtonEventList) _button_events; PT(PointerEventList) _pointer_events; -public: +PUBLISHED: enum State { S_unknown, S_up, S_down }; -PUBLISHED: class ButtonState { public: INLINE ButtonState(); From 9d2e1f92cb6c5e0631d22e838ceb96bb5db45059 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 17 Dec 2017 16:29:03 +0100 Subject: [PATCH 37/82] input: fix for Windows compilation, reenable raw input --- panda/src/device/inputDevice.cxx | 21 ++++++ panda/src/device/inputDevice.h | 1 + panda/src/device/xInputDevice.cxx | 2 +- panda/src/display/graphicsWindowInputDevice.I | 10 +++ panda/src/display/graphicsWindowInputDevice.h | 1 + panda/src/event/pointerEvent.I | 35 ---------- panda/src/event/pointerEvent.h | 7 +- panda/src/event/pointerEventList.I | 70 +++++++++---------- panda/src/event/pointerEventList.cxx | 29 ++++++++ panda/src/event/pointerEventList.h | 26 +++---- panda/src/windisplay/winGraphicsWindow.cxx | 43 ++++++------ 11 files changed, 136 insertions(+), 109 deletions(-) diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 6a910de566..eae889bd1b 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -156,6 +156,27 @@ set_pointer_out_of_window(double time) { } } +/** + * Records that a relative mouse movement has taken place. + */ +void InputDevice:: +pointer_moved(double x, double y, double time) { + nassertv(_lock.debug_is_locked()); + _pointer_data._xpos += x; + _pointer_data._ypos += y; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + if (_pointer_events.is_null()) { + _pointer_events = new PointerEventList(); + } + _pointer_events->add_event(_pointer_data._in_window, + _pointer_data._xpos, + _pointer_data._ypos, + x, y, seq, time); + } +} + /** * Sets the state of the indicated button index, where true indicates down, * and false indicates up. This may generate a ButtonEvent if the button has diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 0850d4a1d3..67b6084b77 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -185,6 +185,7 @@ PUBLISHED: protected: void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); + void pointer_moved(double x, double y, double time); void set_button_state(int index, bool down); void set_control_state(int index, double state); void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 9fe3778f8c..d6832adee3 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -378,7 +378,7 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { for (int i = 0; i < 16; ++i) { if (buttons & mask) { // Set the state without triggering a button event. - _buttons[i]._state = (buttons & mask) ? S_down : S_up; + _buttons[i].state = (buttons & mask) ? S_down : S_up; } mask <<= 1; if (i == 10) { diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index ef97c1f974..c53af656f8 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -37,3 +37,13 @@ set_pointer_out_of_window(double time) { LightMutexHolder holder(_lock); InputDevice::set_pointer_out_of_window(time); } + +/** + * To be called by a particular kind of GraphicsWindow to indicate that the + * pointer has moved by the given relative amount. + */ +INLINE void GraphicsWindowInputDevice:: +pointer_moved(double x, double y, double time) { + LightMutexHolder holder(_lock); + InputDevice::pointer_moved(x, y, time); +} diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index b5b6a98a46..733f077813 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -57,6 +57,7 @@ PUBLISHED: INLINE void set_pointer_in_window(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); INLINE void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE void pointer_moved(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); private: typedef pset ButtonsHeld; diff --git a/panda/src/event/pointerEvent.I b/panda/src/event/pointerEvent.I index 1321efe477..835725bd42 100644 --- a/panda/src/event/pointerEvent.I +++ b/panda/src/event/pointerEvent.I @@ -29,41 +29,6 @@ PointerEvent() : { } -/** - * - */ -INLINE PointerEvent:: -PointerEvent(const PointerEvent ©) : - _in_window(copy._in_window), - _xpos(copy._xpos), - _ypos(copy._ypos), - _dx(copy._dx), - _dy(copy._dy), - _length(copy._length), - _direction(copy._direction), - _rotation(copy._rotation), - _sequence(copy._sequence), - _time(copy._time) -{ -} - -/** - * - */ -INLINE void PointerEvent:: -operator = (const PointerEvent ©) { - _in_window = copy._in_window; - _xpos = copy._xpos; - _ypos = copy._ypos; - _dx = copy._dx; - _dy = copy._dy; - _sequence = copy._sequence; - _length = copy._length; - _direction = copy._direction; - _rotation = copy._rotation; - _time = copy._time; -} - /** * The equality operator does not consider time significant. */ diff --git a/panda/src/event/pointerEvent.h b/panda/src/event/pointerEvent.h index f69d8d4223..138c363c46 100644 --- a/panda/src/event/pointerEvent.h +++ b/panda/src/event/pointerEvent.h @@ -25,10 +25,7 @@ class DatagramIterator; */ class EXPCL_PANDA_EVENT PointerEvent { public: - INLINE PointerEvent(); - INLINE PointerEvent(const PointerEvent ©); - INLINE void operator = (const PointerEvent ©); INLINE bool operator == (const PointerEvent &other) const; INLINE bool operator != (const PointerEvent &other) const; @@ -43,8 +40,8 @@ public: bool _in_window; int _xpos; int _ypos; - int _dx; - int _dy; + double _dx; + double _dy; double _length; double _direction; double _rotation; diff --git a/panda/src/event/pointerEventList.I b/panda/src/event/pointerEventList.I index eda98b6473..2d7f3f6d05 100644 --- a/panda/src/event/pointerEventList.I +++ b/panda/src/event/pointerEventList.I @@ -38,7 +38,7 @@ operator = (const PointerEventList ©) { /** * Returns the number of events in the list. */ -INLINE int PointerEventList:: +INLINE size_t PointerEventList:: get_num_events() const { return _events.size(); } @@ -47,90 +47,90 @@ get_num_events() const { * Get the in-window flag of the nth event. */ INLINE bool PointerEventList:: -get_in_window(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._in_window; +get_in_window(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._in_window; } /** * Get the x-coordinate of the nth event. */ INLINE int PointerEventList:: -get_xpos(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._xpos; +get_xpos(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._xpos; } /** * Get the y-coordinate of the nth event. */ INLINE int PointerEventList:: -get_ypos(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._ypos; +get_ypos(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._ypos; } /** - * Get the x-coordinate of the nth event. + * Get the x-delta of the nth event. */ -INLINE int PointerEventList:: -get_dx(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._dx; +INLINE double PointerEventList:: +get_dx(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._dx; } /** - * Get the y-coordinate of the nth event. + * Get the y-delta of the nth event. */ -INLINE int PointerEventList:: -get_dy(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._dy; +INLINE double PointerEventList:: +get_dy(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._dy; } /** * Get the length of the nth event. */ INLINE double PointerEventList:: -get_length(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._length; +get_length(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._length; } /** * Get the direction of the nth event. */ INLINE double PointerEventList:: -get_direction(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._direction; +get_direction(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._direction; } /** * Get the rotation of the nth event. */ INLINE double PointerEventList:: -get_rotation(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._rotation; +get_rotation(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._rotation; } /** * Get the sequence number of the nth event. */ INLINE int PointerEventList:: -get_sequence(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._sequence; +get_sequence(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._sequence; } /** * Get the timestamp of the nth event. */ INLINE double PointerEventList:: -get_time(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._time; +get_time(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._time; } /** diff --git a/panda/src/event/pointerEventList.cxx b/panda/src/event/pointerEventList.cxx index fc376bc886..105c094753 100644 --- a/panda/src/event/pointerEventList.cxx +++ b/panda/src/event/pointerEventList.cxx @@ -110,6 +110,35 @@ add_event(bool in_win, int xpos, int ypos, int seq, double time) { _events.push_back(pe); } +/** + * Adds a new event to the end of the list based on the given mouse movement. + */ +void PointerEventList:: +add_event(bool in_win, int xpos, int ypos, double xdelta, double ydelta, int seq, double time) { + PointerEvent pe; + pe._in_window = in_win; + pe._xpos = xpos; + pe._ypos = ypos; + pe._dx = xdelta; + pe._dy = ydelta; + pe._sequence = seq; + pe._time = time; + pe._length = csqrt(xdelta*xdelta + ydelta*ydelta); + if (pe._length > 0.0) { + pe._direction = normalize_angle(rad_2_deg(catan2(-ydelta,xdelta))); + } else if (!_events.empty()) { + pe._direction = _events.back()._direction; + } else { + pe._direction = 0.0; + } + if (!_events.empty()) { + pe._rotation = delta_angle(_events.back()._direction, pe._direction); + } else { + pe._rotation = 0.0; + } + _events.push_back(pe); +} + /** * Returns true if the trail loops around the specified point. */ diff --git a/panda/src/event/pointerEventList.h b/panda/src/event/pointerEventList.h index 7c2400c769..552ccca32f 100644 --- a/panda/src/event/pointerEventList.h +++ b/panda/src/event/pointerEventList.h @@ -34,21 +34,23 @@ class EXPCL_PANDA_EVENT PointerEventList : public ParamValueBase { PUBLISHED: INLINE PointerEventList(); - INLINE int get_num_events() const; - INLINE bool get_in_window(int n) const; - INLINE int get_xpos(int n) const; - INLINE int get_ypos(int n) const; - INLINE int get_dx(int n) const; - INLINE int get_dy(int n) const; - INLINE int get_sequence(int n) const; - INLINE double get_length(int n) const; - INLINE double get_direction(int n) const; - INLINE double get_rotation(int n) const; - INLINE double get_time(int n) const; + INLINE size_t get_num_events() const; + INLINE bool get_in_window(size_t n) const; + INLINE int get_xpos(size_t n) const; + INLINE int get_ypos(size_t n) const; + INLINE double get_dx(size_t n) const; + INLINE double get_dy(size_t n) const; + INLINE int get_sequence(size_t n) const; + INLINE double get_length(size_t n) const; + INLINE double get_direction(size_t n) const; + INLINE double get_rotation(size_t n) const; + INLINE double get_time(size_t n) const; INLINE void clear(); INLINE void pop_front(); - void add_event(bool in_win, int xpos, int ypos, int seq, double time); + void add_event(bool in_win, int xpos, int ypos, int seq, double time); + void add_event(bool in_win, int xpos, int ypos, double xdelta, double ydelta, + int seq, double time); bool encircles(int x, int y) const; double total_turns(double sec) const; diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index f76f1baad9..c8115891a7 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -617,15 +617,16 @@ initialize_input_devices() { char *pound3 = pound2 ? strchr(pound2+1,'#') : 0; if (pound3) *pound3 = 0; for (char *p = psName; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { + if (!isalnum(*p)) { *p = '_'; } } if (pound2) *pound2 = '.'; _input_device_handle[_input_devices.size()] = pRawInputDeviceList[i].hDevice; - //GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_only(this, psName); - //device.set_pointer_in_window(0,0); - //add_input_device(device); + + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_only(this, psName); + device->set_pointer_in_window(0, 0); + add_input_device(device); } } } @@ -2654,53 +2655,53 @@ handle_raw_input(HRAWINPUT hraw) { if (raw->header.hDevice == 0) { return; } -/* - for (int i = 1; i < (int)(_input_devices.size()); ++i) { + + for (size_t i = 1; i < _input_devices.size(); ++i) { if (_input_device_handle[i] == raw->header.hDevice) { + PT(GraphicsWindowInputDevice) input = + DCAST(GraphicsWindowInputDevice, _input_devices[i]); + int adjx = raw->data.mouse.lLastX; int adjy = raw->data.mouse.lLastY; if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - _input_devices[i].set_pointer_in_window(adjx, adjy); + input->set_pointer_in_window(adjx, adjy); } else { - //int oldx = _input_devices[i].get_raw_pointer().get_x(); - //int oldy = _input_devices[i].get_raw_pointer().get_y(); - //_input_devices[i].set_pointer_in_window(oldx + adjx, oldy + adjy); + input->pointer_moved(adjx, adjy); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) { - _input_devices[i].button_down(MouseButton::button(0), get_message_time()); + input->button_down(MouseButton::button(0), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) { - _input_devices[i].button_up(MouseButton::button(0), get_message_time()); + input->button_up(MouseButton::button(0), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) { - _input_devices[i].button_down(MouseButton::button(2), get_message_time()); + input->button_down(MouseButton::button(2), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) { - _input_devices[i].button_up(MouseButton::button(2), get_message_time()); + input->button_up(MouseButton::button(2), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) { - _input_devices[i].button_down(MouseButton::button(1), get_message_time()); + input->button_down(MouseButton::button(1), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) { - _input_devices[i].button_up(MouseButton::button(1), get_message_time()); + input->button_up(MouseButton::button(1), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) { - _input_devices[i].button_down(MouseButton::button(3), get_message_time()); + input->button_down(MouseButton::button(3), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) { - _input_devices[i].button_up(MouseButton::button(3), get_message_time()); + input->button_up(MouseButton::button(3), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) { - _input_devices[i].button_down(MouseButton::button(4), get_message_time()); + input->button_down(MouseButton::button(4), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) { - _input_devices[i].button_up(MouseButton::button(4), get_message_time()); + input->button_up(MouseButton::button(4), get_message_time()); } } } -*/ } /** From ef9803b25ea77d046aaf98516df7bc12eac2a0f8 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 22 Dec 2017 14:59:52 +0100 Subject: [PATCH 38/82] Changes to support compiling input-overhaul branch on macOS --- makepanda/makepanda.py | 1 + panda/src/cocoadisplay/cocoaGraphicsWindow.h | 1 + panda/src/cocoadisplay/cocoaGraphicsWindow.mm | 58 ++++++++++--------- panda/src/display/subprocessWindow.cxx | 19 +++--- panda/src/display/subprocessWindow.h | 1 + 5 files changed, 43 insertions(+), 37 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 75261047f9..a3293aa37c 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2359,6 +2359,7 @@ def WriteConfigSettings(): dtool_config["HAVE_GLX"] = 'UNDEF' dtool_config["IS_LINUX"] = 'UNDEF' dtool_config["HAVE_VIDEO4LINUX"] = 'UNDEF' + dtool_config["PHAVE_LINUX_INPUT_H"] = 'UNDEF' dtool_config["IS_OSX"] = '1' # 10.4 had a broken ucontext implementation if int(platform.mac_ver()[0][3]) <= 4: diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.h b/panda/src/cocoadisplay/cocoaGraphicsWindow.h index 6432ecc3e8..9d31a65c14 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.h +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.h @@ -88,6 +88,7 @@ private: NSView *_view; NSUInteger _modifier_keys; CGDirectDisplayID _display; + PT(GraphicsWindowInputDevice) _input; bool _mouse_hidden; bool _context_needs_update; diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index ebc9255d63..1f29e9928e 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -65,9 +65,10 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _fullscreen_mode = NULL; _windowed_mode = NULL; - GraphicsWindowInputDevice device = + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); - add_input_device(device); + _input_devices.push_back(device.p()); + _input = move(device); CocoaGraphicsPipe *cocoa_pipe; DCAST_INTO_V(cocoa_pipe, _pipe); @@ -927,7 +928,7 @@ set_properties_now(WindowProperties &properties) { if (properties.has_cursor_hidden()) { if (properties.get_cursor_hidden() != _properties.get_cursor_hidden()) { - if (properties.get_cursor_hidden() && _input_devices[0].get_pointer().get_in_window()) { + if (properties.get_cursor_hidden() && _input->get_pointer().get_in_window()) { [NSCursor hide]; _mouse_hidden = true; } else if (_mouse_hidden) { @@ -1502,7 +1503,7 @@ handle_key_event(NSEvent *event) { // flaws are: - OS eats unmodified F11, F12, scroll lock, pause - no up // events for caps lock - no robust way to distinguish updown for modkeys if ([event type] == NSKeyUp) { - _input_devices[0].raw_button_up(raw_button); + _input->raw_button_up(raw_button); } else if ([event type] == NSFlagsChanged) { bool down = false; @@ -1524,15 +1525,15 @@ handle_key_event(NSEvent *event) { down = (modifierFlags & 0x0010); } else if (raw_button == KeyboardButton::caps_lock()) { // Emulate down-up, annoying hack! - _input_devices[0].raw_button_down(raw_button); + _input->raw_button_down(raw_button); } if (down) { - _input_devices[0].raw_button_down(raw_button); + _input->raw_button_down(raw_button); } else { - _input_devices[0].raw_button_up(raw_button); + _input->raw_button_up(raw_button); } } else if (![event isARepeat]) { - _input_devices[0].raw_button_down(raw_button); + _input->raw_button_down(raw_button); } } @@ -1556,7 +1557,7 @@ handle_key_event(NSEvent *event) { if ([event type] == NSKeyDown) { NSString *origstr = [event characters]; c = [str characterAtIndex: 0]; - _input_devices[0].keystroke(c); + _input->keystroke(c); } } @@ -1585,9 +1586,9 @@ handle_key_event(NSEvent *event) { // Let's get it off our chest. if ([event type] == NSKeyUp) { - _input_devices[0].button_up(button); + _input->button_up(button); } else { - _input_devices[0].button_down(button); + _input->button_down(button); } } @@ -1598,9 +1599,9 @@ void CocoaGraphicsWindow:: handle_modifier(NSUInteger modifierFlags, NSUInteger mask, ButtonHandle button) { if ((modifierFlags ^ _modifier_keys) & mask) { if (modifierFlags & mask) { - _input_devices[0].button_down(button); + _input->button_down(button); } else { - _input_devices[0].button_up(button); + _input->button_up(button); } } } @@ -1612,14 +1613,14 @@ handle_modifier(NSUInteger modifierFlags, NSUInteger mask, ButtonHandle button) void CocoaGraphicsWindow:: handle_mouse_button_event(int button, bool down) { if (down) { - _input_devices[0].button_down(MouseButton::button(button)); + _input->button_down(MouseButton::button(button)); #ifndef NDEBUG cocoadisplay_cat.spam() << "Mouse button " << button << " down\n"; #endif } else { - _input_devices[0].button_up(MouseButton::button(button)); + _input->button_up(MouseButton::button(button)); #ifndef NDEBUG cocoadisplay_cat.spam() @@ -1638,7 +1639,7 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) { if (absolute) { if (cocoadisplay_cat.is_spam()) { - if (in_window != _input_devices[0].get_pointer().get_in_window()) { + if (in_window != _input->get_pointer().get_in_window()) { if (in_window) { cocoadisplay_cat.spam() << "Mouse pointer entered window\n"; } else { @@ -1653,7 +1654,7 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) { } else { // We received deltas, so add it to the current mouse position. - MouseData md = _input_devices[0].get_pointer(); + MouseData md = _input->get_pointer(); nx = md.get_x() + x; ny = md.get_y() + y; } @@ -1679,8 +1680,11 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) { } } - _input_devices[0].set_pointer(in_window, nx, ny, - ClockObject::get_global_clock()->get_frame_time()); + if (in_window) { + _input->set_pointer_in_window(nx, ny); + } else { + _input->set_pointer_out_of_window(); + } if (in_window != _mouse_hidden && _properties.get_cursor_hidden()) { // Hide the cursor if the mouse enters the window, and unhide it when the @@ -1703,20 +1707,20 @@ handle_wheel_event(double x, double y) { << "Wheel delta " << x << ", " << y << "\n"; if (y > 0.0) { - _input_devices[0].button_down(MouseButton::wheel_up()); - _input_devices[0].button_up(MouseButton::wheel_up()); + _input->button_down(MouseButton::wheel_up()); + _input->button_up(MouseButton::wheel_up()); } else if (y < 0.0) { - _input_devices[0].button_down(MouseButton::wheel_down()); - _input_devices[0].button_up(MouseButton::wheel_down()); + _input->button_down(MouseButton::wheel_down()); + _input->button_up(MouseButton::wheel_down()); } // TODO: check if this is correct, I don't own a MacBook if (x > 0.0) { - _input_devices[0].button_down(MouseButton::wheel_right()); - _input_devices[0].button_up(MouseButton::wheel_right()); + _input->button_down(MouseButton::wheel_right()); + _input->button_up(MouseButton::wheel_right()); } else if (x < 0.0) { - _input_devices[0].button_down(MouseButton::wheel_left()); - _input_devices[0].button_up(MouseButton::wheel_left()); + _input->button_down(MouseButton::wheel_left()); + _input->button_up(MouseButton::wheel_left()); } } diff --git a/panda/src/display/subprocessWindow.cxx b/panda/src/display/subprocessWindow.cxx index d001f3b1f6..e643791817 100644 --- a/panda/src/display/subprocessWindow.cxx +++ b/panda/src/display/subprocessWindow.cxx @@ -35,9 +35,8 @@ SubprocessWindow(GraphicsEngine *engine, GraphicsPipe *pipe, GraphicsOutput *host) : GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host) { - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard/mouse"); - _input_devices.push_back(device); + _input = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard/mouse"); + _input_devices.push_back(_input.p()); // This will be an offscreen buffer that we use to render the actual // contents. @@ -79,9 +78,9 @@ process_events() { while (_swbuffer->get_event(swb_event)) { // Deal with this event. if (swb_event._flags & SubprocessWindowBuffer::EF_mouse_position) { - _input_devices[0].set_pointer_in_window(swb_event._x, swb_event._y); + _input->set_pointer_in_window(swb_event._x, swb_event._y); } else if ((swb_event._flags & SubprocessWindowBuffer::EF_has_mouse) == 0) { - _input_devices[0].set_pointer_out_of_window(); + _input->set_pointer_out_of_window(); } unsigned int diff = swb_event._flags ^ _last_event_flags; @@ -108,14 +107,14 @@ process_events() { int keycode; button = translate_key(keycode, swb_event._code, swb_event._flags); if (keycode != 0 && swb_event._type != SubprocessWindowBuffer::ET_button_up) { - _input_devices[0].keystroke(keycode); + _input->keystroke(keycode); } } if (swb_event._type == SubprocessWindowBuffer::ET_button_up) { - _input_devices[0].button_up(button); + _input->button_up(button); } else if (swb_event._type == SubprocessWindowBuffer::ET_button_down) { - _input_devices[0].button_down(button); + _input->button_down(button); } } } @@ -552,9 +551,9 @@ translate_key(int &keycode, int os_code, unsigned int flags) const { void SubprocessWindow:: transition_button(unsigned int flags, ButtonHandle button) { if (flags) { - _input_devices[0].button_down(button); + _input->button_down(button); } else { - _input_devices[0].button_up(button); + _input->button_up(button); } } diff --git a/panda/src/display/subprocessWindow.h b/panda/src/display/subprocessWindow.h index 8065721f42..3d81877f91 100644 --- a/panda/src/display/subprocessWindow.h +++ b/panda/src/display/subprocessWindow.h @@ -75,6 +75,7 @@ private: private: PT(GraphicsBuffer) _buffer; PT(Texture) _texture; + PT(GraphicsWindowInputDevice) _input; int _fd; size_t _mmap_size; From 12cfc93b79f79742d66c16aac8cb8d16804ab659 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 22 Dec 2017 15:08:39 +0100 Subject: [PATCH 39/82] input: let InputDevice manage axis remapping, add C_twist axis --- panda/src/device/evdevInputDevice.cxx | 44 ++++--------- panda/src/device/evdevInputDevice.h | 6 -- panda/src/device/inputDevice.I | 4 +- panda/src/device/inputDevice.cxx | 81 +++++++++++++++++++++++- panda/src/device/inputDevice.h | 14 +++- panda/src/device/linuxJoystickDevice.cxx | 33 +++++----- panda/src/device/xInputDevice.cxx | 38 +++++++---- panda/src/vrpn/vrpnButton.cxx | 2 +- 8 files changed, 152 insertions(+), 70 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index ca2247c012..21ce8d2fa1 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -371,17 +371,10 @@ init_device() { } if (has_axes) { - AxisRange range; - range._scale = 1.0; - range._bias = 0.0; - _axis_ranges.resize(ABS_CNT, range); - - for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { + ControlAxis axis = C_none; if (i >= ABS_HAT0X) { - set_control_map(i, C_none); - // Emulate D-Pad buttons if necessary. if (i == ABS_HAT0X && emulate_dpad) { _dpad_x_axis = i; @@ -395,38 +388,25 @@ init_device() { _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); } else if (i == ABS_HAT0X) { - set_control_map(i, C_hat_x); + axis = C_hat_x; } else if (i == ABS_HAT0Y) { - set_control_map(i, C_hat_y); + axis = C_hat_y; } } else { - set_control_map(i, axis_map[i]); + axis = axis_map[i]; //cerr << "Axis " << axis_map[i] << " is mapped by the driver to " << i << "\n"; } // Check the initial value and ranges. struct input_absinfo absinfo; if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { - double factor, bias; - if (absinfo.minimum < 0) { - // Centered, eg. for sticks. - factor = 2.0 / (absinfo.maximum - absinfo.minimum); - bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum); - } else { - // 0-based, eg. for triggers. - factor = 1.0 / absinfo.maximum; - bias = 0.0; - } - // Flip Y axis to match Windows implementation. if (i == ABS_Y || i == ABS_RY) { - factor = -factor; - bias = -bias; + swap(absinfo.minimum, absinfo.maximum); } - _axis_ranges[i]._scale = factor; - _axis_ranges[i]._bias = bias; - _controls[i].state = fma(absinfo.value, factor, bias); + add_control(axis, absinfo.minimum, absinfo.maximum); + control_changed(axis, absinfo.value); if (absinfo.value != 0) { all_values_zero = false; @@ -562,13 +542,13 @@ process_events() { case EV_ABS: if (code == _dpad_x_axis) { - set_button_state(_dpad_left_button, events[i].value < 0); - set_button_state(_dpad_left_button+1, events[i].value > 0); + button_changed(_dpad_left_button, events[i].value < 0); + button_changed(_dpad_left_button+1, events[i].value > 0); } else if (code == _dpad_y_axis) { - set_button_state(_dpad_up_button, events[i].value < 0); - set_button_state(_dpad_up_button+1, events[i].value > 0); + button_changed(_dpad_up_button, events[i].value < 0); + button_changed(_dpad_up_button+1, events[i].value > 0); } - set_control_state(code, fma(events[i].value, _axis_ranges[code]._scale, _axis_ranges[code]._bias)); + control_changed(code, events[i].value); break; case EV_KEY: diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 5a604b8617..849d72ae27 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -44,12 +44,6 @@ private: int _ff_strong; int _ff_weak; - struct AxisRange { - double _scale; - double _bias; - }; - pvector _axis_ranges; - // These are used for D-pad emulation. int _dpad_x_axis; int _dpad_y_axis; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index b512b973a8..c10ba4a172 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -461,6 +461,8 @@ INLINE InputDevice::AnalogState:: AnalogState() : axis(C_none), state(0.0), - known(false) + known(false), + _scale(1.0), + _bias(0.0) { } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index eae889bd1b..a5b1f1cd45 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -114,6 +114,45 @@ get_pointer_events() { return result; } + +/** + * Called by the implementation to add a new known control. + */ +void InputDevice:: +add_control(ControlAxis axis, int minimum, int maximum, bool centered) { + AnalogState state; + state.axis = axis; + if (centered) { + // Centered, eg. for sticks. + state._scale = 2.0 / (maximum - minimum); + state._bias = (maximum + minimum) / (double)(minimum - maximum); + } else { + // 0-based, eg. for triggers. + state._scale = 1.0 / maximum; + state._bias = 0.0; + } + _controls.push_back(state); +} + +/** + * Called by the implementation to add a new known control. This version + * tries to guess whether the control is centered or not. + */ +void InputDevice:: +add_control(ControlAxis axis, int minimum, int maximum) { + bool centered = (minimum < 0) + || axis == C_left_x + || axis == C_left_y + || axis == C_right_x + || axis == C_right_y + || axis == C_x + || axis == C_y + || axis == C_wheel + || axis == C_twist + || axis == C_rudder; + add_control(axis, minimum, maximum, centered); +} + /** * Records that a mouse movement has taken place. */ @@ -165,6 +204,11 @@ pointer_moved(double x, double y, double time) { _pointer_data._xpos += x; _pointer_data._ypos += y; + if (device_cat.is_spam() && (x != 0 || y != 0)) { + device_cat.spam() + << "Pointer moved by " << x << " x " << y << "\n"; + } + if (_enable_pointer_events) { int seq = _event_sequence++; if (_pointer_events.is_null()) { @@ -184,7 +228,7 @@ pointer_moved(double x, double y, double time) { * while this call is made. */ void InputDevice:: -set_button_state(int index, bool down) { +button_changed(int index, bool down) { nassertv(_lock.debug_is_locked()); nassertv(index >= 0); if (index >= (int)_buttons.size()) { @@ -244,6 +288,34 @@ set_control_state(int index, double state) { _controls[index].known = true; } +/** + * Like set_control_state, but instead passes a raw, unscaled value. + */ +void InputDevice:: +control_changed(int index, int state) { + nassertv(_lock.debug_is_locked()); + nassertv(index >= 0); + if (index >= (int)_controls.size()) { + _controls.resize(index + 1, AnalogState()); + } + + double new_state = fma((double)state, _controls[index]._scale, _controls[index]._bias); + + if (device_cat.is_spam() && _controls[index].state != new_state) { + device_cat.spam() + << "Changed control " << index; + + if (_controls[index].axis != C_none) { + device_cat.spam(false) << " (" << _controls[index].axis << ")"; + } + + device_cat.spam(false) << " to " << new_state << " (raw value " << state << ")\n"; + } + + _controls[index].state = new_state; + _controls[index].known = true; +} + /** * Records that a tracker movement has taken place. */ @@ -467,6 +539,9 @@ operator << (ostream &out, InputDevice::DeviceClass dc) { case InputDevice::DC_hmd: out << "hmd"; break; + + case InputDevice::DC_COUNT: + break; } return out; } @@ -518,6 +593,10 @@ operator << (ostream &out, InputDevice::ControlAxis axis) { out << "throttle"; break; + case InputDevice::C_twist: + out << "twist"; + break; + case InputDevice::C_rudder: out << "rudder"; break; diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 67b6084b77..7f0e459bc3 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -108,6 +108,7 @@ PUBLISHED: C_y, C_trigger, C_throttle, + C_twist, C_rudder, C_hat_x, C_hat_y, @@ -183,11 +184,18 @@ PUBLISHED: virtual void output(ostream &out) const; protected: + // Called during the constructor to add new controls or buttons + void add_control(ControlAxis axis, int minimum, int maximum, bool centered); + void add_control(ControlAxis axis, int minimum, int maximum); + void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); + void pointer_moved(double x, double y, double time); - void set_button_state(int index, bool down); + void button_changed(int index, bool down); + void control_changed(int index, int value); void set_control_state(int index, double state); + void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); virtual void do_set_vibration(double low, double high); @@ -271,6 +279,10 @@ PUBLISHED: ControlAxis axis; double state; bool known; + + public: + double _scale; + double _bias; }; typedef pvector Controls; Controls _controls; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index fc2e2ba7b1..8cd612f2d7 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -322,6 +322,18 @@ open_device() { break; } _controls[i].axis = axis; + + if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { + // We'd like to use 0.0 to indicate the resting position. + _controls[i]._scale = 1.0 / 65534.0; + _controls[i]._bias = 0.5; + } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { + _controls[i]._scale = 1.0 / -32767.0; + _controls[i]._bias = 0.0; + } else { + _controls[i]._scale = 1.0 / 32767.0; + _controls[i]._bias = 0.0; + } } } @@ -436,27 +448,18 @@ process_events() { int index = events[i].number; if (events[i].type & JS_EVENT_BUTTON) { - set_button_state(index, (events[i].value != 0)); + button_changed(index, (events[i].value != 0)); } else if (events[i].type & JS_EVENT_AXIS) { if (index == _dpad_x_axis) { - set_button_state(_dpad_left_button, events[i].value < -1000); - set_button_state(_dpad_left_button+1, events[i].value > 1000); + button_changed(_dpad_left_button, events[i].value < -1000); + button_changed(_dpad_left_button+1, events[i].value > 1000); } else if (index == _dpad_y_axis) { - set_button_state(_dpad_up_button, events[i].value < -1000); - set_button_state(_dpad_up_button+1, events[i].value > 1000); + button_changed(_dpad_up_button, events[i].value < -1000); + button_changed(_dpad_up_button+1, events[i].value > 1000); } - ControlAxis axis = _controls[index].axis; - - if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { - // We'd like to use 0.0 to indicate the resting position. - set_control_state(index, (events[i].value + 32767) / 65534.0); - } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { - set_control_state(index, events[i].value / -32767.0); - } else { - set_control_state(index, events[i].value / 32767.0); - } + control_changed(index, events[i].value); } } diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index d6832adee3..04d88996f5 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -290,6 +290,18 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { break; } + _controls[0]._scale = 1.0 / 255.0; + _controls[1]._scale = 1.0 / 255.0; + _controls[2]._scale = 1.0 / 32767.5; + _controls[3]._scale = 1.0 / 32767.5; + _controls[4]._scale = 1.0 / 32767.5; + _controls[5]._scale = 1.0 / 32767.5; + + _controls[2]._bias = 0.5 / 32767.5; + _controls[3]._bias = 0.5 / 32767.5; + _controls[4]._bias = 0.5 / 32767.5; + _controls[5]._bias = 0.5 / 32767.5; + if (caps.Flags & XINPUT_CAPS_NO_NAVIGATION) { set_button_map(0, ButtonHandle::none()); set_button_map(1, ButtonHandle::none()); @@ -387,12 +399,12 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { } } - set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); - set_control_state(1, state.Gamepad.bRightTrigger / 255.0); - set_control_state(2, state.Gamepad.sThumbLX / 32767.0); - set_control_state(3, state.Gamepad.sThumbLY / 32767.0); - set_control_state(4, state.Gamepad.sThumbRX / 32767.0); - set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + control_changed(0, state.Gamepad.bLeftTrigger); + control_changed(1, state.Gamepad.bRightTrigger); + control_changed(2, state.Gamepad.sThumbLX); + control_changed(3, state.Gamepad.sThumbLY); + control_changed(4, state.Gamepad.sThumbRX); + control_changed(5, state.Gamepad.sThumbRY); _last_buttons = buttons; _last_packet = state.dwPacketNumber; @@ -453,7 +465,7 @@ do_poll() { WORD mask = 1; for (int i = 0; i < 16; ++i) { if (changed_buttons & mask) { - set_button_state(i, (state.Gamepad.wButtons & mask) != 0); + button_changed(i, (state.Gamepad.wButtons & mask) != 0); } mask <<= 1; if (i == 10) { @@ -462,12 +474,12 @@ do_poll() { } } - set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); - set_control_state(1, state.Gamepad.bRightTrigger / 255.0); - set_control_state(2, (state.Gamepad.sThumbLX + 0.5) / 32767.5); - set_control_state(3, (state.Gamepad.sThumbLY + 0.5) / 32767.5); - set_control_state(4, (state.Gamepad.sThumbRX + 0.5) / 32767.5); - set_control_state(5, (state.Gamepad.sThumbRY + 0.5) / 32767.5); + control_changed(0, state.Gamepad.bLeftTrigger); + control_changed(1, state.Gamepad.bRightTrigger); + control_changed(2, state.Gamepad.sThumbLX); + control_changed(3, state.Gamepad.sThumbLY); + control_changed(4, state.Gamepad.sThumbRX); + control_changed(5, state.Gamepad.sThumbRY); _last_buttons = state.Gamepad.wButtons; _last_packet = state.dwPacketNumber; diff --git a/panda/src/vrpn/vrpnButton.cxx b/panda/src/vrpn/vrpnButton.cxx index c1505619d8..60aff640e7 100644 --- a/panda/src/vrpn/vrpnButton.cxx +++ b/panda/src/vrpn/vrpnButton.cxx @@ -103,6 +103,6 @@ vrpn_button_callback(void *userdata, const vrpn_BUTTONCB info) { Devices::iterator di; for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnButtonDevice *device = (*di); - device->set_button_state(info.button, info.state != 0); + device->button_changed(info.button, info.state != 0); } } From 920f1b75e35c4ba185c75ed97964359de75b6f84 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 22 Dec 2017 15:32:03 +0100 Subject: [PATCH 40/82] input: support macOS gamepads, joysticks, raw keyboards/mice I tested gamepads with a wireless 360 controller and a wired Xbox One controller using the 360Controller driver. I tested the joystick support using a T.Flight Hotas X. --- panda/src/device/inputDeviceManager.cxx | 66 +- panda/src/device/inputDeviceManager.h | 10 + panda/src/device/ioKitInputDevice.cxx | 772 +++++++++++++++++++++++ panda/src/device/ioKitInputDevice.h | 55 ++ panda/src/device/p3device_composite1.cxx | 5 +- 5 files changed, 905 insertions(+), 3 deletions(-) create mode 100644 panda/src/device/ioKitInputDevice.cxx create mode 100644 panda/src/device/ioKitInputDevice.h diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index edcc2a7903..d7f6a04955 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -12,6 +12,7 @@ */ #include "inputDeviceManager.h" +#include "ioKitInputDevice.h" #include "linuxJoystickDevice.h" #include "throw_event.h" @@ -108,6 +109,48 @@ InputDeviceManager() : _connected_devices.add_device(&_xinput_device3); } } +#elif defined(__APPLE__) +InputDeviceManager:: +InputDeviceManager() { + _hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (!_hid_manager) { + device_cat.error() + << "Failed to create an IOHIDManager.\n"; + return; + } + + // The types of devices we're interested in. + int page = kHIDPage_GenericDesktop; + int usages[] = {kHIDUsage_GD_GamePad, + kHIDUsage_GD_Joystick, + kHIDUsage_GD_Mouse, + kHIDUsage_GD_Keyboard, + kHIDUsage_GD_MultiAxisController, 0}; + int *usage = usages; + + // This giant mess is necessary to create an array of match dictionaries + // that will match the devices we're interested in. + CFMutableArrayRef match = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + nassertv(match); + while (*usage) { + CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFNumberRef page_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), page_ref); + CFRelease(page_ref); + CFNumberRef usage_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, usage); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), usage_ref); + CFRelease(usage_ref); + CFArrayAppendValue(match, dict); + CFRelease(dict); + ++usage; + } + IOHIDManagerSetDeviceMatchingMultiple(_hid_manager, match); + CFRelease(match); + + IOHIDManagerRegisterDeviceMatchingCallback(_hid_manager, on_match_device, this); + IOHIDManagerScheduleWithRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); + IOHIDManagerOpen(_hid_manager, kIOHIDOptionsTypeNone); +} #else InputDeviceManager:: InputDeviceManager() { @@ -124,6 +167,10 @@ InputDeviceManager:: close(_inotify_fd); _inotify_fd = -1; } +#elif defined(__APPLE__) + IOHIDManagerUnscheduleFromRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); + IOHIDManagerClose(_hid_manager, kIOHIDOptionsTypeNone); + CFRelease(_hid_manager); #endif } @@ -318,7 +365,8 @@ remove_device(InputDevice *device) { } /** - * Polls the system to see if there are any new devices. + * Polls the system to see if there are any new devices. In some + * implementations this is a no-op. */ void InputDeviceManager:: update() { @@ -426,3 +474,19 @@ update() { } #endif } + +#if defined(__APPLE__) && !defined(CPPPARSER) +void InputDeviceManager:: +on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) { + InputDeviceManager *mgr = (InputDeviceManager *)ctx; + nassertv(mgr != nullptr); + nassertv(device); + + PT(InputDevice) input_device = new IOKitInputDevice(device); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered input device " << *input_device << "\n"; + } + mgr->add_device(input_device); +} +#endif diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 9596a841d2..a262b60d28 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -23,6 +23,10 @@ #include "xinputDevice.h" #endif +#ifdef __APPLE__ +#include +#endif + /** * This class keeps track of all the devices on a system, and sends out events * when a device has been hot-plugged. @@ -71,6 +75,12 @@ private: double _last_detection; #endif +#if defined(__APPLE__) && !defined(CPPPARSER) + IOHIDManagerRef _hid_manager; + + static void on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device); +#endif + InputDeviceSet _connected_devices; static InputDeviceManager *_global_ptr; diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx new file mode 100644 index 0000000000..ec0522b1a0 --- /dev/null +++ b/panda/src/device/ioKitInputDevice.cxx @@ -0,0 +1,772 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ioKitInputDevice.cxx + * @author rdb + * @date 2017-12-21 + */ + +#include "ioKitInputDevice.h" + +#if defined(__APPLE__) && !defined(CPPPARSER) + +#include + +#include "keyboardButton.h" +#include "gamepadButton.h" +#include "mouseButton.h" + +static void removal_callback(void *ctx, IOReturn result, void *sender) { + IOKitInputDevice *input_device = (IOKitInputDevice *)ctx; + nassertv(input_device != nullptr); + input_device->on_remove(); +} + +/** + * Protected constructor. + */ +IOKitInputDevice:: +IOKitInputDevice(IOHIDDeviceRef device) : + _device(device), + _hat_element(nullptr), + _pointer_x(nullptr), + _pointer_y(nullptr), + _scroll_wheel(nullptr), + _pointer_x_timestamp(0), + _pointer_y_timestamp(0), + _scroll_wheel_timestamp(0) { + nassertv(device); + + char buffer[4096]; + + CFStringRef name = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); + if (name) { + CFStringGetCString(name, buffer, sizeof(buffer), kCFStringEncodingUTF8); + _name = buffer; + } + + CFStringRef mfg = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey)); + if (mfg) { + CFStringGetCString(mfg, buffer, sizeof(buffer), kCFStringEncodingUTF8); + _manufacturer = buffer; + } + + CFStringRef serial = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDSerialNumberKey)); + if (serial) { + CFStringGetCString(serial, buffer, sizeof(buffer), kCFStringEncodingUTF8); + _serial_number = buffer; + } + + CFNumberRef vendor = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)); + if (vendor) { + int32_t value = 0; + CFNumberGetValue(vendor, kCFNumberSInt32Type, &value); + _vendor_id = (unsigned short)value; + } + + CFNumberRef product = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)); + if (product) { + int32_t value = 0; + CFNumberGetValue(product, kCFNumberSInt32Type, &value); + _product_id = (unsigned short)value; + } + + if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse)) { + _device_class = DC_mouse; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) { + _device_class = DC_keyboard; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad)) { + _device_class = DC_gamepad; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_Sim_FlightStick)) { + _device_class = DC_flight_stick; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_GD_Joystick)) { + _device_class = DC_flight_stick; + } else if (_vendor_id == 0x044f && _product_id == 0xb108) { + // T.Flight Hotas X + _device_class = DC_flight_stick; + } + + CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, 0); + CFIndex count = CFArrayGetCount(elements); + for (CFIndex i = 0; i < count; ++i) { + IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i); + parse_element(element); + } + CFRelease(elements); + + if (_hat_element != nullptr) { + _hat_x_axis = _controls.size(); + add_control(C_hat_x, -1, 1, true); + add_control(C_hat_y, -1, 1, true); + } + + if (_pointer_x != nullptr && _pointer_y != nullptr) { + _flags |= IDF_has_pointer; + } + + _is_connected = true; + IOHIDDeviceRegisterRemovalCallback(device, removal_callback, this); +} + +/** + * + */ +IOKitInputDevice:: +~IOKitInputDevice() { +} + +/** + * The nonstatic version of on_remove_device. + */ +void IOKitInputDevice:: +on_remove() { + { + LightMutexHolder holder(_lock); + if (!_is_connected) { + return; + } + _is_connected = false; + } + + if (device_cat.is_debug()) { + device_cat.debug() + << "Removed input device " << *this << "\n"; + } + + IOHIDDeviceClose(_device, kIOHIDOptionsTypeNone); + + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + nassertv(mgr != nullptr); + mgr->remove_device(this); +} + +/** + * + */ +void IOKitInputDevice:: +parse_element(IOHIDElementRef element) { + ButtonHandle handle = ButtonHandle::none(); + ControlAxis axis = C_none; + uint32_t page = IOHIDElementGetUsagePage(element); + uint32_t usage = IOHIDElementGetUsage(element); + + switch (IOHIDElementGetType(element)) { + case kIOHIDElementTypeInput_Misc: + switch (page) { + case kHIDPage_GenericDesktop: + switch (usage) { + case kHIDUsage_GD_X: + if (_device_class == DC_gamepad) { + axis = C_left_x; + } else if (_device_class == DC_mouse) { + _pointer_x = element; + return; + } else { + axis = C_x; + } + break; + case kHIDUsage_GD_Y: + if (_device_class == DC_gamepad) { + axis = C_left_y; + } else if (_device_class == DC_mouse) { + _pointer_y = element; + return; + } else { + axis = C_y; + } + break; + case kHIDUsage_GD_Z: + if (_device_class == DC_gamepad) { + axis = C_left_trigger; + } else { + axis = C_throttle; + } + break; + case kHIDUsage_GD_Rx: + axis = C_right_x; + break; + case kHIDUsage_GD_Ry: + axis = C_right_y; + break; + case kHIDUsage_GD_Rz: + if (_device_class == DC_gamepad) { + axis = C_right_trigger; + } else { + axis = C_twist; + } + break; + case kHIDUsage_GD_Slider: + axis = C_rudder; + break; + case kHIDUsage_GD_Dial: + break; + case kHIDUsage_GD_Wheel: + _scroll_wheel = element; + return; + case kHIDUsage_GD_Hatswitch: + _hat_element = element; + return; + case kHIDUsage_GD_DPadUp: + handle = GamepadButton::dpad_up(); + break; + case kHIDUsage_GD_DPadDown: + handle = GamepadButton::dpad_down(); + break; + case kHIDUsage_GD_DPadRight: + handle = GamepadButton::dpad_right(); + break; + case kHIDUsage_GD_DPadLeft: + handle = GamepadButton::dpad_left(); + break; + case 0xffffffffu: + return; + default: + break; + } + break; + + case kHIDPage_Simulation: + switch (usage) { + case kHIDUsage_Sim_Rudder: + axis = C_rudder; + break; + case kHIDUsage_Sim_Throttle: + axis = C_throttle; + break; + case kHIDUsage_Sim_Accelerator: + axis = C_accelerator; + break; + case kHIDUsage_Sim_Brake: + axis = C_brake; + break; + } + break; + } + if (axis != C_none) { + int min = IOHIDElementGetLogicalMin(element); + int max = IOHIDElementGetLogicalMax(element); + if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { + // T.Flight Hotas X throttle is reversed and can go backwards. + add_control(axis, max, min, true); + } else if (axis == C_y || axis == C_left_y || axis == C_right_y) { + // We'd like to reverse the Y axis to match the XInput behavior. + add_control(axis, max, min); + } else { + add_control(axis, min, max); + } + + _analog_elements.push_back(element); + } + break; + + case kIOHIDElementTypeInput_Button: + switch (page) { + case kHIDPage_GenericDesktop: + switch (usage) { + case kHIDUsage_GD_DPadUp: + handle = GamepadButton::dpad_up(); + break; + case kHIDUsage_GD_DPadDown: + handle = GamepadButton::dpad_down(); + break; + case kHIDUsage_GD_DPadRight: + handle = GamepadButton::dpad_right(); + break; + case kHIDUsage_GD_DPadLeft: + handle = GamepadButton::dpad_left(); + break; + default: + break; + } + break; + + case kHIDPage_KeyboardOrKeypad: + switch (usage) { + case kHIDUsage_KeyboardA: + case kHIDUsage_KeyboardB: + case kHIDUsage_KeyboardC: + case kHIDUsage_KeyboardD: + case kHIDUsage_KeyboardE: + case kHIDUsage_KeyboardF: + case kHIDUsage_KeyboardG: + case kHIDUsage_KeyboardH: + case kHIDUsage_KeyboardI: + case kHIDUsage_KeyboardJ: + case kHIDUsage_KeyboardK: + case kHIDUsage_KeyboardL: + case kHIDUsage_KeyboardM: + case kHIDUsage_KeyboardN: + case kHIDUsage_KeyboardO: + case kHIDUsage_KeyboardP: + case kHIDUsage_KeyboardQ: + case kHIDUsage_KeyboardR: + case kHIDUsage_KeyboardS: + case kHIDUsage_KeyboardT: + case kHIDUsage_KeyboardU: + case kHIDUsage_KeyboardV: + case kHIDUsage_KeyboardW: + case kHIDUsage_KeyboardX: + case kHIDUsage_KeyboardY: + case kHIDUsage_KeyboardZ: + handle = KeyboardButton::ascii_key('a' + (usage - kHIDUsage_KeyboardA)); + break; + case kHIDUsage_Keyboard1: + handle = KeyboardButton::ascii_key('1'); + break; + case kHIDUsage_Keyboard2: + handle = KeyboardButton::ascii_key('2'); + break; + case kHIDUsage_Keyboard3: + handle = KeyboardButton::ascii_key('3'); + break; + case kHIDUsage_Keyboard4: + handle = KeyboardButton::ascii_key('4'); + break; + case kHIDUsage_Keyboard5: + handle = KeyboardButton::ascii_key('5'); + break; + case kHIDUsage_Keyboard6: + handle = KeyboardButton::ascii_key('6'); + break; + case kHIDUsage_Keyboard7: + handle = KeyboardButton::ascii_key('7'); + break; + case kHIDUsage_Keyboard8: + handle = KeyboardButton::ascii_key('8'); + break; + case kHIDUsage_Keyboard9: + handle = KeyboardButton::ascii_key('9'); + break; + case kHIDUsage_Keyboard0: + handle = KeyboardButton::ascii_key('0'); + break; + case kHIDUsage_KeyboardReturnOrEnter: + handle = KeyboardButton::enter(); + break; + case kHIDUsage_KeyboardEscape: + handle = KeyboardButton::escape(); + break; + case kHIDUsage_KeyboardDeleteOrBackspace: + handle = KeyboardButton::backspace(); + break; + case kHIDUsage_KeyboardTab: + handle = KeyboardButton::tab(); + break; + case kHIDUsage_KeyboardSpacebar: + handle = KeyboardButton::ascii_key(' '); + break; + case kHIDUsage_KeyboardHyphen: + handle = KeyboardButton::ascii_key('-'); + break; + case kHIDUsage_KeyboardEqualSign: + handle = KeyboardButton::ascii_key('='); + break; + case kHIDUsage_KeyboardOpenBracket: + handle = KeyboardButton::ascii_key('['); + break; + case kHIDUsage_KeyboardCloseBracket: + handle = KeyboardButton::ascii_key(']'); + break; + case kHIDUsage_KeyboardBackslash: + handle = KeyboardButton::ascii_key('\\'); + break; + case kHIDUsage_KeyboardNonUSPound: + handle = KeyboardButton::ascii_key('$'); + break; + case kHIDUsage_KeyboardSemicolon: + handle = KeyboardButton::ascii_key(';'); + break; + case kHIDUsage_KeyboardQuote: + handle = KeyboardButton::ascii_key('\''); + break; + case kHIDUsage_KeyboardGraveAccentAndTilde: + handle = KeyboardButton::ascii_key('`'); + break; + case kHIDUsage_KeyboardComma: + handle = KeyboardButton::ascii_key(','); + break; + case kHIDUsage_KeyboardPeriod: + handle = KeyboardButton::ascii_key('.'); + break; + case kHIDUsage_KeyboardSlash: + handle = KeyboardButton::ascii_key('/'); + break; + case kHIDUsage_KeyboardCapsLock: + handle = KeyboardButton::caps_lock(); + break; + case kHIDUsage_KeyboardF1: + handle = KeyboardButton::f1(); + break; + case kHIDUsage_KeyboardF2: + handle = KeyboardButton::f2(); + break; + case kHIDUsage_KeyboardF3: + handle = KeyboardButton::f3(); + break; + case kHIDUsage_KeyboardF4: + handle = KeyboardButton::f4(); + break; + case kHIDUsage_KeyboardF5: + handle = KeyboardButton::f5(); + break; + case kHIDUsage_KeyboardF6: + handle = KeyboardButton::f6(); + break; + case kHIDUsage_KeyboardF7: + handle = KeyboardButton::f7(); + break; + case kHIDUsage_KeyboardF8: + handle = KeyboardButton::f8(); + break; + case kHIDUsage_KeyboardF9: + handle = KeyboardButton::f9(); + break; + case kHIDUsage_KeyboardF10: + handle = KeyboardButton::f10(); + break; + case kHIDUsage_KeyboardF11: + handle = KeyboardButton::f11(); + break; + case kHIDUsage_KeyboardF12: + handle = KeyboardButton::f12(); + break; + case kHIDUsage_KeyboardPrintScreen: + handle = KeyboardButton::print_screen(); + break; + case kHIDUsage_KeyboardScrollLock: + handle = KeyboardButton::scroll_lock(); + break; + case kHIDUsage_KeyboardPause: + handle = KeyboardButton::pause(); + break; + case kHIDUsage_KeyboardInsert: + handle = KeyboardButton::insert(); + break; + case kHIDUsage_KeyboardHome: + handle = KeyboardButton::home(); + break; + case kHIDUsage_KeyboardPageUp: + handle = KeyboardButton::page_up(); + break; + case kHIDUsage_KeyboardDeleteForward: + handle = KeyboardButton::del(); + break; + case kHIDUsage_KeyboardEnd: + handle = KeyboardButton::end(); + break; + case kHIDUsage_KeyboardPageDown: + handle = KeyboardButton::page_down(); + break; + case kHIDUsage_KeyboardRightArrow: + handle = KeyboardButton::right(); + break; + case kHIDUsage_KeyboardLeftArrow: + handle = KeyboardButton::left(); + break; + case kHIDUsage_KeyboardDownArrow: + handle = KeyboardButton::down(); + break; + case kHIDUsage_KeyboardUpArrow: + handle = KeyboardButton::up(); + break; + case kHIDUsage_KeypadNumLock: + handle = KeyboardButton::num_lock(); + break; + case kHIDUsage_KeypadSlash: + handle = KeyboardButton::ascii_key('/'); + break; + case kHIDUsage_KeypadAsterisk: + handle = KeyboardButton::ascii_key('*'); + break; + case kHIDUsage_KeypadHyphen: + handle = KeyboardButton::ascii_key('-'); + break; + case kHIDUsage_KeypadPlus: + handle = KeyboardButton::ascii_key('+'); + break; + case kHIDUsage_KeypadEnter: + handle = KeyboardButton::enter(); + break; + case kHIDUsage_Keypad1: + handle = KeyboardButton::ascii_key('1'); + break; + case kHIDUsage_Keypad2: + handle = KeyboardButton::ascii_key('2'); + break; + case kHIDUsage_Keypad3: + handle = KeyboardButton::ascii_key('3'); + break; + case kHIDUsage_Keypad4: + handle = KeyboardButton::ascii_key('4'); + break; + case kHIDUsage_Keypad5: + handle = KeyboardButton::ascii_key('5'); + break; + case kHIDUsage_Keypad6: + handle = KeyboardButton::ascii_key('6'); + break; + case kHIDUsage_Keypad7: + handle = KeyboardButton::ascii_key('7'); + break; + case kHIDUsage_Keypad8: + handle = KeyboardButton::ascii_key('8'); + break; + case kHIDUsage_Keypad9: + handle = KeyboardButton::ascii_key('9'); + break; + case kHIDUsage_Keypad0: + handle = KeyboardButton::ascii_key('0'); + break; + case kHIDUsage_KeypadPeriod: + handle = KeyboardButton::ascii_key('.'); + break; + case kHIDUsage_KeyboardNonUSBackslash: + handle = KeyboardButton::ascii_key('\\'); + break; + case kHIDUsage_KeypadEqualSign: + handle = KeyboardButton::ascii_key('='); + break; + case kHIDUsage_KeyboardF13: + handle = KeyboardButton::f13(); + break; + case kHIDUsage_KeyboardF14: + handle = KeyboardButton::f14(); + break; + case kHIDUsage_KeyboardF15: + handle = KeyboardButton::f15(); + break; + case kHIDUsage_KeyboardF16: + handle = KeyboardButton::f16(); + break; + case kHIDUsage_KeyboardExecute: + break; + case kHIDUsage_KeyboardHelp: + handle = KeyboardButton::help(); + break; + case kHIDUsage_KeyboardMenu: + handle = KeyboardButton::menu(); + break; + case kHIDUsage_KeypadComma: + handle = KeyboardButton::ascii_key(','); + break; + case kHIDUsage_KeypadEqualSignAS400: + handle = KeyboardButton::ascii_key('='); + break; + case kHIDUsage_KeyboardReturn: + handle = KeyboardButton::enter(); + break; + case kHIDUsage_KeyboardLeftControl: + handle = KeyboardButton::lcontrol(); + break; + case kHIDUsage_KeyboardLeftShift: + handle = KeyboardButton::lshift(); + break; + case kHIDUsage_KeyboardLeftAlt: + handle = KeyboardButton::lalt(); + break; + case kHIDUsage_KeyboardLeftGUI: + handle = KeyboardButton::lmeta(); + break; + case kHIDUsage_KeyboardRightControl: + handle = KeyboardButton::rcontrol(); + break; + case kHIDUsage_KeyboardRightShift: + handle = KeyboardButton::rshift(); + break; + case kHIDUsage_KeyboardRightAlt: + handle = KeyboardButton::ralt(); + break; + case kHIDUsage_KeyboardRightGUI: + handle = KeyboardButton::rmeta(); + break; + default: + break; + } + break; + + case kHIDPage_Button: + if (_device_class == DC_gamepad) { + // These seem to be the button mappings exposed by the 360Controller + // driver I don't know if other drivers do the same thing at all. + static const ButtonHandle gamepad_buttons[] = { + ButtonHandle::none(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::lstick(), + GamepadButton::rstick(), + GamepadButton::start(), + GamepadButton::back(), + GamepadButton::guide(), + GamepadButton::dpad_up(), + GamepadButton::dpad_down(), + GamepadButton::dpad_left(), + GamepadButton::dpad_right(), + }; + if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + handle = gamepad_buttons[usage]; + } + } else if (_device_class == DC_mouse) { + // In Panda, wheel and right button are flipped around... + int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); + handle = MouseButton::button(button); + } + break; + } + _buttons.push_back(ButtonState(handle)); + _button_elements.push_back(element); + break; + + case kIOHIDElementTypeInput_Axis: + break; + + case kIOHIDElementTypeInput_ScanCodes: + break; + + case kIOHIDElementTypeOutput: + break; + + case kIOHIDElementTypeFeature: + break; + + case kIOHIDElementTypeCollection: + { + // This doesn't seem to be necessary and instead leads to duplication of + // axes and buttons. + /* + CFArrayRef children = IOHIDElementGetChildren(element); + CFIndex count = CFArrayGetCount(children); + for (CFIndex i = 0; i < count; ++i) { + IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(children, i); + parse_element(element, depth + 2); + }*/ + } + break; + } +} + +/** + * Polls the input device for new activity, to ensure it contains the latest + * events. This will only have any effect for some types of input devices; + * others may be updated automatically, and this method will be a no-op. + */ +void IOKitInputDevice:: +do_poll() { + for (size_t i = 0; i < _button_elements.size(); ++i) { + IOHIDValueRef value_ref; + if (!_button_elements[i]) continue; + if (IOHIDDeviceGetValue(_device, _button_elements[i], &value_ref) == kIOReturnSuccess) { + int value = IOHIDValueGetIntegerValue(value_ref); + button_changed(i, value != 0); + } + } + + for (size_t i = 0; i < _analog_elements.size(); ++i) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _analog_elements[i], &value_ref) == kIOReturnSuccess) { + int value = IOHIDValueGetIntegerValue(value_ref); + control_changed(i, value); + } + } + + if (_hat_element != nullptr) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _hat_element, &value_ref) == kIOReturnSuccess) { + int value = IOHIDValueGetIntegerValue(value_ref); + int x = 0; + int y = 0; + switch (value) { + case 0: + x = 0; + y = -1; + break; + case 1: + x = 1; + y = -1; + break; + case 2: + x = 1; + y = 0; + break; + case 3: + x = 1; + y = 1; + break; + case 4: + x = 0; + y = 1; + break; + case 5: + x = -1; + y = 1; + break; + case 6: + x = -1; + y = 0; + break; + case 7: + x = -1; + y = -1; + break; + default: + break; + } + control_changed(_hat_x_axis, x); + control_changed(_hat_x_axis + 1, y); + } + } + + int x = 0, y = 0; + if (_pointer_x != nullptr) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _pointer_x, &value_ref) == kIOReturnSuccess) { + uint64_t timestamp = IOHIDValueGetTimeStamp(value_ref); + if (timestamp != _pointer_x_timestamp) { + x = IOHIDValueGetIntegerValue(value_ref); + _pointer_x_timestamp = timestamp; + } + } + } + if (_pointer_y != nullptr) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _pointer_y, &value_ref) == kIOReturnSuccess) { + uint64_t timestamp = IOHIDValueGetTimeStamp(value_ref); + if (timestamp != _pointer_y_timestamp) { + y = IOHIDValueGetIntegerValue(value_ref); + _pointer_y_timestamp = timestamp; + } + } + } + if (x != 0 || y != 0) { + pointer_moved(x, y, ClockObject::get_global_clock()->get_frame_time()); + } + + // Do we have a scroll wheel axis? + if (_scroll_wheel != nullptr) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _scroll_wheel, &value_ref) == kIOReturnSuccess) { + uint64_t timestamp = IOHIDValueGetTimeStamp(value_ref); + if (timestamp != _scroll_wheel_timestamp) { + int value = IOHIDValueGetIntegerValue(value_ref); + if (value != 0) { + // Just fire off a rapid down/up event. + double time = ClockObject::get_global_clock()->get_frame_time(); + ButtonHandle handle = (value > 0) ? MouseButton::wheel_up() : MouseButton::wheel_down(); + _button_events->add_event(ButtonEvent(handle, ButtonEvent::T_down, time)); + _button_events->add_event(ButtonEvent(handle, ButtonEvent::T_up, time)); + } + _scroll_wheel_timestamp = timestamp; + } + } + } +} + +#endif // __APPLE__ diff --git a/panda/src/device/ioKitInputDevice.h b/panda/src/device/ioKitInputDevice.h new file mode 100644 index 0000000000..1dfde3d034 --- /dev/null +++ b/panda/src/device/ioKitInputDevice.h @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ioKitInputDevice.h + * @author rdb + * @date 2017-12-21 + */ + +#ifndef IOKITINPUTDEVICE_H +#define IOKITINPUTDEVICE_H + +#include "pandabase.h" +#include "inputDevice.h" + +#if defined(__APPLE__) && !defined(CPPPARSER) + +#include + +/** + * This implementation uses the IOKit HID code that was introduced with macOS + * 10.5 to interface with USB HID devices. + */ +class EXPCL_PANDA_DEVICE IOKitInputDevice FINAL : public InputDevice { +public: + IOKitInputDevice(IOHIDDeviceRef device); + ~IOKitInputDevice(); + + void on_remove(); + +private: + void parse_element(IOHIDElementRef element); + + virtual void do_poll(); + + IOHIDDeviceRef _device; + pvector _button_elements; + pvector _analog_elements; + IOHIDElementRef _hat_element; + int _hat_x_axis; + IOHIDElementRef _pointer_x; + IOHIDElementRef _pointer_y; + IOHIDElementRef _scroll_wheel; + uint64_t _pointer_x_timestamp; + uint64_t _pointer_y_timestamp; + uint64_t _scroll_wheel_timestamp; +}; + +#endif // __APPLE__ + +#endif diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 90c0b54133..b9895f7880 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -8,6 +8,7 @@ #include "evdevInputDevice.cxx" #include "inputDevice.cxx" #include "inputDeviceManager.cxx" -#include "inputDeviceSet.cxx" -#include "linuxJoystickDevice.cxx" #include "inputDeviceNode.cxx" +#include "inputDeviceSet.cxx" +#include "ioKitInputDevice.cxx" +#include "linuxJoystickDevice.cxx" From ba8cbfd0564c6fa55f03cc2250cf449bf5067491 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 22 Dec 2017 17:09:20 +0100 Subject: [PATCH 41/82] input: fixes for Linux scoring and axis/button mapping This adds a list of known devices so that we can assign custom behavior to them. I tested this with the T.Flight Hotas X flight stick and the NVIDIA Shield Controller. --- panda/src/device/evdevInputDevice.cxx | 359 +++++++++++++++----------- panda/src/device/evdevInputDevice.h | 3 + panda/src/device/inputDevice.cxx | 8 +- panda/src/device/inputDevice.h | 4 +- 4 files changed, 223 insertions(+), 151 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 21ce8d2fa1..b6c8c193b9 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -25,52 +25,33 @@ #define test_bit(bit, array) ((array)[(bit)>>3] & (1<<((bit)&7))) -static InputDevice::ControlAxis axis_map[] = { - // ABS_X = 0x00 - InputDevice::C_left_x, - // ABS_Y = 0x01 - InputDevice::C_left_y, - // ABS_Z = 0x02 - InputDevice::C_left_trigger, - // ABS_RX = 0x03 - InputDevice::C_right_x, - // ABS_RY = 0x04 - InputDevice::C_right_y, - // ABS_RZ = 0x05 - InputDevice::C_right_trigger, - // ABS_THROTTLE = 0x06 - InputDevice::C_throttle, - // ABS_RUDDER = 0x07 - InputDevice::C_rudder, - // ABS_WHEEL = 0x08 - InputDevice::C_wheel, - // ABS_GAS = 0x09 - InputDevice::C_accelerator, - // ABS_BRAKE = 0x0a - InputDevice::C_brake, +enum QuirkBits { + // Right stick uses Z and Rz inputs. + QB_rstick_from_z = 1, - InputDevice::C_none, - InputDevice::C_none, - InputDevice::C_none, - InputDevice::C_none, - InputDevice::C_none, + // Throttle goes from -1 to 1 rather than from 0 to 1. + QB_centered_throttle = 2, - // ABS_HAT0X = 0x10 - InputDevice::C_hat_x, - // ABS_HAT0Y = 0x11 - InputDevice::C_hat_y, + // Throttle is reversed. + QB_reversed_throttle = 4, - // ABS_HAT1X = 0x12 - // ABS_HAT1Y = 0x13 - // ABS_HAT2X = 0x14 - // ABS_HAT2Y = 0x15 - // ABS_HAT3X = 0x16 - // ABS_HAT3Y = 0x17 - // ABS_PRESSURE = 0x18 - // ABS_DISTANCE = 0x19 - // ABS_TILT_X = 0x1a - // ABS_TILT_Y = 0x1b - // ABS_TOOL_WIDTH = 0x1c + // Only consider the device "connected" if all axes are non-zero. + QB_connect_if_nonzero = 8, +}; + +static const struct DeviceMapping { + unsigned short vendor; + unsigned short product; + InputDevice::DeviceClass device_class; + int quirks; +} mapping_presets[] = { + // NVIDIA Shield Controller + {0x0955, 0x7214, InputDevice::DC_gamepad, QB_rstick_from_z}, + // T.Flight Hotas X + {0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle}, + // Xbox 360 Wireless Controller + {0x045e, 0x0719, InputDevice::DC_gamepad, QB_connect_if_nonzero}, + {0}, }; TypeHandle EvdevInputDevice::_type_handle; @@ -214,6 +195,8 @@ bool EvdevInputDevice:: init_device() { nassertr(_fd >= 0, false); + LightMutexHolder holder(_lock); + uint8_t evtypes[(EV_CNT + 7) >> 3]; memset(evtypes, 0, sizeof(evtypes)); char name[128]; @@ -262,76 +245,89 @@ init_device() { has_axes = true; } + // Do we have a preset device mapping? + int quirks = 0; + const DeviceMapping *mapping = mapping_presets; + while (mapping->vendor != 0) { + if (_vendor_id == mapping->vendor && _product_id == mapping->product) { + _device_class = mapping->device_class; + quirks = mapping->quirks; + break; + } + ++mapping; + } // Try to detect which type of device we have here - int device_scores[DC_COUNT]; - memset(device_scores, 0, sizeof(device_scores)); + if (_device_class == DC_unknown) { + int device_scores[DC_COUNT]; + memset(device_scores, 0, sizeof(device_scores)); - // Test for specific keys - if (test_bit(BTN_GAMEPAD, keys)) { - device_scores[DC_gamepad] += 5; - device_scores[DC_steering_wheel] += 5; - device_scores[DC_flight_stick] += 5; - } + // Test for specific keys + if (test_bit(BTN_GAMEPAD, keys) && test_bit(ABS_X, axes) && test_bit(ABS_RX, axes)) { + device_scores[DC_gamepad] += 5; + device_scores[DC_steering_wheel] += 5; + device_scores[DC_flight_stick] += 5; + } - if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) { - device_scores[DC_steering_wheel] += 10; - } - if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) { - device_scores[DC_steering_wheel] += 10; - } - if (test_bit(BTN_JOYSTICK, keys)) { - device_scores[DC_flight_stick] += 10; - } - if (test_bit(BTN_MOUSE, keys)) { - device_scores[DC_mouse] += 20; - } - uint8_t unknown_keys[] = {KEY_POWER}; - for (int i = 0; i < 1; i++) { - if (test_bit(unknown_keys[i], keys)) { - if (unknown_keys[i] == KEY_POWER) { + if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) { + device_scores[DC_steering_wheel] += 10; + } + if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) { + device_scores[DC_steering_wheel] += 10; + } + if (test_bit(BTN_JOYSTICK, keys) && test_bit(ABS_X, axes)) { + device_scores[DC_flight_stick] += 10; + } + if (test_bit(BTN_MOUSE, keys) && test_bit(EV_REL, evtypes)) { + device_scores[DC_mouse] += 20; + } + uint8_t unknown_keys[] = {KEY_POWER}; + for (int i = 0; i < 1; i++) { + if (test_bit(unknown_keys[i], keys)) { + if (unknown_keys[i] == KEY_POWER) { + } + device_scores[DC_unknown] += 20; } - device_scores[DC_unknown] += 20; } - } - if (_flags & IDF_has_keyboard) { - device_scores[DC_keyboard] += 20; - } + if (_flags & IDF_has_keyboard) { + device_scores[DC_keyboard] += 20; + } - // Test for specific name tags - string lowercase_name = _name; - for(int x=0; x<_name.length(); x++) { - lowercase_name[x]=tolower(lowercase_name[x]); - } - if (lowercase_name.find("gamepad") != string::npos) { - device_scores[DC_gamepad] += 10; - } - if (lowercase_name.find("wheel") != string::npos) { - device_scores[DC_steering_wheel] += 10; - } - if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) { - device_scores[DC_mouse] += 10; - } - if (lowercase_name.find("keyboard") != string::npos) { - device_scores[DC_keyboard] += 10; - } - // List of lowercase names that occur in unknown devices - string unknown_names[] = {"video bus", "power button", "sleep button"}; - for(int i = 0; i < 3; i++) { - if (lowercase_name.find(unknown_names[i]) != string::npos) { - device_scores[DC_unknown] += 20; + // Test for specific name tags + string lowercase_name = _name; + for(int x=0; x<_name.length(); x++) { + lowercase_name[x]=tolower(lowercase_name[x]); + } + if (lowercase_name.find("gamepad") != string::npos) { + device_scores[DC_gamepad] += 10; + } + if (lowercase_name.find("wheel") != string::npos) { + device_scores[DC_steering_wheel] += 10; + } + if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) { + device_scores[DC_mouse] += 10; + } + if (lowercase_name.find("keyboard") != string::npos) { + device_scores[DC_keyboard] += 10; + } + // List of lowercase names that occur in unknown devices + string unknown_names[] = {"video bus", "power button", "sleep button"}; + for(int i = 0; i < 3; i++) { + if (lowercase_name.find(unknown_names[i]) != string::npos) { + device_scores[DC_unknown] += 20; + } } - } - // Check which device type got the most points - size_t highest_score = 0; - for (size_t i = 0; i < DC_COUNT; i++) { - if (device_scores[i] > highest_score) { - highest_score = device_scores[i]; - _device_class = (DeviceClass)i; + // Check which device type got the most points + size_t highest_score = 0; + for (size_t i = 0; i < DC_COUNT; i++) { + if (device_scores[i] > highest_score) { + highest_score = device_scores[i]; + _device_class = (DeviceClass)i; + } } + //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; } - //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; if (_device_class != DC_gamepad) { emulate_dpad = false; @@ -343,70 +339,140 @@ init_device() { memset(states, 0, sizeof(states)); ioctl(_fd, EVIOCGKEY(sizeof(states)), states); - int bi = 0; for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { - ButtonHandle mapped = map_button(i); - set_button_map(bi, mapped); + ButtonState button; + button.handle = map_button(i); - if (mapped == ButtonHandle::none()) { + int button_index = (int)_buttons.size(); + if (button.handle == ButtonHandle::none()) { if (device_cat.is_debug()) { device_cat.debug() << "Unmapped /dev/input/event" << _index - << " button " << bi << ": 0x" << hex << i << dec << "\n"; + << " button " << button_index << ": 0x" << hex << i << dec << "\n"; } } if (test_bit(i, states)) { - _buttons[bi].state = S_down; + button.state = S_down; all_values_zero = false; } else { - _buttons[bi].state = S_up; + button.state = S_up; } - if (_buttons[bi].handle == GamepadButton::dpad_left()) { + if (button.handle == GamepadButton::dpad_left()) { emulate_dpad = false; } - ++bi; + _buttons.push_back(button); + if (i >= _button_indices.size()) { + _button_indices.resize(i + 1, -1); + } + _button_indices[i] = button_index; } } } if (has_axes) { + _control_indices.resize(num_bits, -1); + for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { ControlAxis axis = C_none; - if (i >= ABS_HAT0X) { - // Emulate D-Pad buttons if necessary. - if (i == ABS_HAT0X && emulate_dpad) { + switch (i) { + case ABS_X: + axis = InputDevice::C_left_x; + break; + case ABS_Y: + axis = InputDevice::C_left_y; + break; + case ABS_Z: + if (quirks & QB_rstick_from_z) { + axis = InputDevice::C_right_x; + } else if (_device_class == DC_gamepad) { + axis = InputDevice::C_left_trigger; + } else { + axis = InputDevice::C_throttle; + } + break; + case ABS_RX: + if ((quirks & QB_rstick_from_z) == 0) { + axis = InputDevice::C_right_x; + } + break; + case ABS_RY: + if ((quirks & QB_rstick_from_z) == 0) { + axis = InputDevice::C_right_y; + } + break; + case ABS_RZ: + if (quirks & QB_rstick_from_z) { + axis = InputDevice::C_right_y; + } else if (_device_class == DC_gamepad) { + axis = InputDevice::C_right_trigger; + } else { + axis = InputDevice::C_twist; + } + break; + case ABS_THROTTLE: + axis = InputDevice::C_rudder; + break; + case ABS_RUDDER: + axis = InputDevice::C_rudder; + break; + case ABS_WHEEL: + axis = InputDevice::C_wheel; + break; + case ABS_GAS: + if (_device_class == DC_gamepad) { + axis = InputDevice::C_right_trigger; + } else { + axis = InputDevice::C_accelerator; + } + break; + case ABS_BRAKE: + if (_device_class == DC_gamepad) { + axis = InputDevice::C_left_trigger; + } else { + axis = InputDevice::C_brake; + } + break; + case ABS_HAT0X: + if (emulate_dpad) { _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_right())); - - } else if (i == ABS_HAT0Y && emulate_dpad) { + } else { + axis = C_hat_x; + } + break; + case ABS_HAT0Y: + if (emulate_dpad) { _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); - } else if (i == ABS_HAT0X) { - axis = C_hat_x; - } else if (i == ABS_HAT0Y) { + } else { axis = C_hat_y; } - } else { - axis = axis_map[i]; - //cerr << "Axis " << axis_map[i] << " is mapped by the driver to " << i << "\n"; + break; } // Check the initial value and ranges. struct input_absinfo absinfo; if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { - // Flip Y axis to match Windows implementation. - if (i == ABS_Y || i == ABS_RY) { - swap(absinfo.minimum, absinfo.maximum); + int index; + // We'd like to reverse the Y axis to match the XInput behavior. + // Also T.Flight Hotas X throttle is reversed and can go backwards. + if (axis == C_y || axis == C_left_y || axis == C_right_y || + (axis == C_throttle && (quirks & QB_reversed_throttle) != 0)) { + swap(absinfo.maximum, absinfo.minimum); } - - add_control(axis, absinfo.minimum, absinfo.maximum); - control_changed(axis, absinfo.value); + if (axis == C_throttle && (quirks & QB_centered_throttle) != 0) { + index = add_control(axis, absinfo.maximum, absinfo.minimum, true); + } else { + index = add_control(axis, absinfo.minimum, absinfo.maximum); + } + control_changed(index, absinfo.value); + _control_indices[i] = index; if (absinfo.value != 0) { all_values_zero = false; @@ -472,7 +538,7 @@ init_device() { // Special-case fix for Xbox 360 Wireless Receiver: the Linux kernel // driver always reports 4 connected gamepads, regardless of the number // of gamepads actually present. This hack partially remedies this. - if (all_values_zero && _vendor_id == 0x045e && _product_id == 0x0719) { + if (all_values_zero && (quirks & QB_connect_if_nonzero) != 0) { _is_connected = false; } else { _is_connected = true; @@ -520,6 +586,7 @@ process_events() { bool have_pointer = false; double time = ClockObject::get_global_clock()->get_frame_time(); ButtonHandle button; + int index; // It seems that some devices send a single EV_SYN event when being // unplugged. Boo. Ignore it. @@ -548,26 +615,18 @@ process_events() { button_changed(_dpad_up_button, events[i].value < 0); button_changed(_dpad_up_button+1, events[i].value > 0); } - control_changed(code, events[i].value); + nassertd(code >= 0 && code < _control_indices.size()) break; + index = _control_indices[code]; + if (index >= 0) { + control_changed(index, events[i].value); + } break; case EV_KEY: - button = map_button(code); - _button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time)); - // set the buttons state - State state; - if (events[i].value) { - state = S_down; - } else { - state = S_up; - } - for (int i = 0; i < _buttons.size(); ++i) { - if (get_button(i).handle == button) { - //NOTE: set_button_state doesn't work correct here. - //set_button_state(i, state); - _buttons[i].state = state; - break; - } + nassertd(code >= 0 && code < _button_indices.size()) break; + index = _button_indices[code]; + if (index >= 0) { + button_changed(index, events[i].value != 0); } break; @@ -723,6 +782,14 @@ map_button(int code) { }; return keyboard_map[code]; + } else if (code == KEY_BACK) { + // Used by NVIDIA Shield Controller + return GamepadButton::back(); + + } else if (code == KEY_SEARCH) { + // Used by NVIDIA Shield Controller + return GamepadButton::guide(); + } else if (code < 0x100) { return ButtonHandle::none(); diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 849d72ae27..bd249b4d0e 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -44,6 +44,9 @@ private: int _ff_strong; int _ff_weak; + pvector _control_indices; + pvector _button_indices; + // These are used for D-pad emulation. int _dpad_x_axis; int _dpad_y_axis; diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index a5b1f1cd45..d1a5576c58 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -118,7 +118,7 @@ get_pointer_events() { /** * Called by the implementation to add a new known control. */ -void InputDevice:: +int InputDevice:: add_control(ControlAxis axis, int minimum, int maximum, bool centered) { AnalogState state; state.axis = axis; @@ -131,14 +131,16 @@ add_control(ControlAxis axis, int minimum, int maximum, bool centered) { state._scale = 1.0 / maximum; state._bias = 0.0; } + int index = (int)_controls.size(); _controls.push_back(state); + return index; } /** * Called by the implementation to add a new known control. This version * tries to guess whether the control is centered or not. */ -void InputDevice:: +int InputDevice:: add_control(ControlAxis axis, int minimum, int maximum) { bool centered = (minimum < 0) || axis == C_left_x @@ -150,7 +152,7 @@ add_control(ControlAxis axis, int minimum, int maximum) { || axis == C_wheel || axis == C_twist || axis == C_rudder; - add_control(axis, minimum, maximum, centered); + return add_control(axis, minimum, maximum, centered); } /** diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 7f0e459bc3..3dd5d698af 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -185,8 +185,8 @@ PUBLISHED: protected: // Called during the constructor to add new controls or buttons - void add_control(ControlAxis axis, int minimum, int maximum, bool centered); - void add_control(ControlAxis axis, int minimum, int maximum); + int add_control(ControlAxis axis, int minimum, int maximum, bool centered); + int add_control(ControlAxis axis, int minimum, int maximum); void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); From 58623b09bdaa687b8ab541d451cd35d52dca2e7b Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 3 Jan 2018 14:48:18 +0100 Subject: [PATCH 42/82] input: add missing has_vibration() method to InputDevice --- panda/src/device/inputDevice.I | 10 ++++++++++ panda/src/device/inputDevice.h | 1 + 2 files changed, 11 insertions(+) diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index c10ba4a172..f8be63a9c3 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -118,6 +118,16 @@ has_tracker() const { return ((_flags & IDF_has_tracker) != 0); } +/** + * Returns true if the device has vibration motors that can be controlled by + * calling set_vibration(). + */ +INLINE bool InputDevice:: +has_vibration() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_vibration) != 0); +} + /** * Returns true if the device may be able to provide information about its * battery life. diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 3dd5d698af..f4727e0a9c 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -149,6 +149,7 @@ PUBLISHED: INLINE bool has_pointer() const; INLINE bool has_keyboard() const; INLINE bool has_tracker() const; + INLINE bool has_vibration() const; INLINE bool has_battery() const; INLINE PointerData get_pointer() const; From 4f739e88cf6fa97a7af78b6f935003936e04242d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 14:50:54 +0100 Subject: [PATCH 43/82] input: joystick buttons, use yaw/pitch/roll for axes, more devices Also adds format_* functions to get string from enum value. --- panda/src/device/evdevInputDevice.cxx | 105 +++++++++--- panda/src/device/evdevInputDevice.h | 2 +- panda/src/device/inputDevice.cxx | 196 +++++++++++------------ panda/src/device/inputDevice.h | 25 ++- panda/src/device/linuxJoystickDevice.cxx | 32 ++-- panda/src/device/linuxJoystickDevice.h | 2 +- panda/src/device/xInputDevice.cxx | 6 +- panda/src/putil/gamepadButton.cxx | 30 ++++ panda/src/putil/gamepadButton.h | 6 + 9 files changed, 256 insertions(+), 148 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index b6c8c193b9..67fa1193c7 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -37,6 +37,9 @@ enum QuirkBits { // Only consider the device "connected" if all axes are non-zero. QB_connect_if_nonzero = 8, + + // ABS_THROTTLE maps to rudder + QB_rudder_from_throttle = 16, }; static const struct DeviceMapping { @@ -48,9 +51,25 @@ static const struct DeviceMapping { // NVIDIA Shield Controller {0x0955, 0x7214, InputDevice::DC_gamepad, QB_rstick_from_z}, // T.Flight Hotas X - {0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle}, + {0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle | QB_rudder_from_throttle}, // Xbox 360 Wireless Controller {0x045e, 0x0719, InputDevice::DC_gamepad, QB_connect_if_nonzero}, + // Jess Tech Colour Rumble Pad + {0x0f30, 0x0111, InputDevice::DC_gamepad, 0}, + // 3Dconnexion Space Traveller 3D Mouse + {0x046d, 0xc623, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Pilot 3D Mouse + {0x046d, 0xc625, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Navigator 3D Mouse + {0x046d, 0xc626, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Explorer 3D Mouse + {0x046d, 0xc627, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Navigator for Notebooks + {0x046d, 0xc628, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion SpacePilot Pro 3D Mouse + {0x046d, 0xc629, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Mouse Pro + {0x046d, 0xc62b, InputDevice::DC_3d_mouse, 0}, {0}, }; @@ -329,10 +348,6 @@ init_device() { //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; } - if (_device_class != DC_gamepad) { - emulate_dpad = false; - } - if (has_keys) { // Also check whether the buttons are currently pressed. uint8_t states[(KEY_CNT + 7) >> 3]; @@ -342,7 +357,7 @@ init_device() { for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { ButtonState button; - button.handle = map_button(i); + button.handle = map_button(i, _device_class); int button_index = (int)_buttons.size(); if (button.handle == ButtonHandle::none()) { @@ -378,10 +393,22 @@ init_device() { ControlAxis axis = C_none; switch (i) { case ABS_X: - axis = InputDevice::C_left_x; + if (_device_class == DC_gamepad) { + axis = InputDevice::C_left_x; + } else if (_device_class == DC_flight_stick) { + axis = InputDevice::C_roll; + } else { + axis = InputDevice::C_x; + } break; case ABS_Y: - axis = InputDevice::C_left_y; + if (_device_class == DC_gamepad) { + axis = InputDevice::C_left_y; + } else if (_device_class == DC_flight_stick) { + axis = InputDevice::C_pitch; + } else { + axis = InputDevice::C_y; + } break; case ABS_Z: if (quirks & QB_rstick_from_z) { @@ -408,11 +435,15 @@ init_device() { } else if (_device_class == DC_gamepad) { axis = InputDevice::C_right_trigger; } else { - axis = InputDevice::C_twist; + axis = InputDevice::C_yaw; } break; case ABS_THROTTLE: - axis = InputDevice::C_rudder; + if (quirks & QB_rudder_from_throttle) { + axis = InputDevice::C_rudder; + } else { + axis = InputDevice::C_throttle; + } break; case ABS_RUDDER: axis = InputDevice::C_rudder; @@ -438,20 +469,26 @@ init_device() { if (emulate_dpad) { _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); - } else { - axis = C_hat_x; + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_left())); + _buttons.push_back(ButtonState(GamepadButton::hat_right())); + } } break; case ABS_HAT0Y: if (emulate_dpad) { _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); - } else { - axis = C_hat_y; + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_up())); + _buttons.push_back(ButtonState(GamepadButton::hat_down())); + } } break; } @@ -461,8 +498,9 @@ init_device() { if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { int index; // We'd like to reverse the Y axis to match the XInput behavior. + // Also reverse the yaw axis to match right-hand coordinate system. // Also T.Flight Hotas X throttle is reversed and can go backwards. - if (axis == C_y || axis == C_left_y || axis == C_right_y || + if (axis == C_yaw || axis == C_left_y || axis == C_right_y || (axis == C_throttle && (quirks & QB_reversed_throttle) != 0)) { swap(absinfo.maximum, absinfo.minimum); } @@ -647,7 +685,7 @@ process_events() { * Static function to map an evdev code to a ButtonHandle. */ ButtonHandle EvdevInputDevice:: -map_button(int code) { +map_button(int code, DeviceClass device_class) { if (code >= 0 && code < 0x80) { // See linux/input.h for the source of this mapping. static const ButtonHandle keyboard_map[] = { @@ -802,12 +840,33 @@ map_button(int code) { } else { return MouseButton::button(code - BTN_MOUSE); } + + } else if ((code & 0xfff0) == BTN_JOYSTICK) { + if (device_class == DC_gamepad) { + // Based on "Jess Tech Colour Rumble Pad" + static const ButtonHandle mapping[] = { + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::lshoulder(), + GamepadButton::ltrigger(), + GamepadButton::rshoulder(), + GamepadButton::rtrigger(), + GamepadButton::back(), + GamepadButton::start(), + GamepadButton::lstick(), + GamepadButton::rstick(), + }; + if ((code & 0xf) < 12) { + return mapping[code & 0xf]; + } + } else { + return GamepadButton::joystick(code & 0xf); + } } switch (code) { - case BTN_TRIGGER: - return GamepadButton::trigger(); - case BTN_A: return GamepadButton::action_a(); diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index bd249b4d0e..9ebaef03f2 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -54,7 +54,7 @@ private: int _dpad_up_button; public: - static ButtonHandle map_button(int code); + static ButtonHandle map_button(int code, DeviceClass device_class = DC_unknown); public: static TypeHandle get_class_type() { diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index d1a5576c58..76aa79dc58 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -143,14 +143,17 @@ add_control(ControlAxis axis, int minimum, int maximum, bool centered) { int InputDevice:: add_control(ControlAxis axis, int minimum, int maximum) { bool centered = (minimum < 0) + || axis == C_x + || axis == C_y + || axis == C_z + || axis == C_yaw + || axis == C_pitch + || axis == C_roll || axis == C_left_x || axis == C_left_y || axis == C_right_x || axis == C_right_y - || axis == C_x - || axis == C_y || axis == C_wheel - || axis == C_twist || axis == C_rudder; return add_control(axis, minimum, maximum, centered); } @@ -499,130 +502,125 @@ void InputDevice:: do_poll() { } -ostream & -operator << (ostream &out, InputDevice::DeviceClass dc) { +/** + * Returns a string describing the given device class enumerant. + */ +string InputDevice:: +format_device_class(DeviceClass dc) { switch (dc) { case InputDevice::DC_unknown: - out << "unknown"; - break; + return "unknown"; case InputDevice::DC_virtual: - out << "virtual"; - break; + return "virtual"; case InputDevice::DC_keyboard: - out << "keyboard"; - break; + return "keyboard"; case InputDevice::DC_mouse: - out << "mouse"; - break; + return "mouse"; case InputDevice::DC_touch: - out << "touch"; - break; + return "touch"; case InputDevice::DC_gamepad: - out << "gamepad"; - break; + return "gamepad"; case InputDevice::DC_flight_stick: - out << "flight_stick"; - break; + return "flight_stick"; case InputDevice::DC_steering_wheel: - out << "steering_wheel"; - break; + return "steering_wheel"; case InputDevice::DC_dance_pad: - out << "dance_pad"; - break; + return "dance_pad"; case InputDevice::DC_hmd: - out << "hmd"; - break; + return "hmd"; + + case InputDevice::DC_3d_mouse: + return "3d_mouse"; case InputDevice::DC_COUNT: break; } + return "**invalid**"; +} + +/** + * Returns a string describing the given axis enumerant. + */ +string InputDevice:: +format_axis(ControlAxis axis) { + switch (axis) { + case InputDevice::C_none: + return "none"; + + case InputDevice::C_x: + return "x"; + + case InputDevice::C_y: + return "y"; + + case InputDevice::C_z: + return "z"; + + case InputDevice::C_yaw: + return "yaw"; + + case InputDevice::C_pitch: + return "pitch"; + + case InputDevice::C_roll: + return "roll"; + + case InputDevice::C_left_x: + return "left_x"; + + case InputDevice::C_left_y: + return "left_y"; + + case InputDevice::C_left_trigger: + return "left_trigger"; + + case InputDevice::C_right_x: + return "right_x"; + + case InputDevice::C_right_y: + return "right_y"; + + case InputDevice::C_right_trigger: + return "right_trigger"; + + //case InputDevice::C_trigger: + // return "trigger"; + + case InputDevice::C_throttle: + return "throttle"; + + case InputDevice::C_rudder: + return "rudder"; + + case InputDevice::C_wheel: + return "wheel"; + + case InputDevice::C_accelerator: + return "accelerator"; + + case InputDevice::C_brake: + return "brake"; + } + return "**invalid**"; +} + +ostream & +operator << (ostream &out, InputDevice::DeviceClass dc) { + out << InputDevice::format_device_class(dc); return out; } ostream & operator << (ostream &out, InputDevice::ControlAxis axis) { - switch (axis) { - case InputDevice::C_none: - out << "none"; - break; - - case InputDevice::C_left_x: - out << "left_x"; - break; - - case InputDevice::C_left_y: - out << "left_y"; - break; - - case InputDevice::C_left_trigger: - out << "left_trigger"; - break; - - case InputDevice::C_right_x: - out << "right_x"; - break; - - case InputDevice::C_right_y: - out << "right_y"; - break; - - case InputDevice::C_right_trigger: - out << "right_trigger"; - break; - - case InputDevice::C_x: - out << "x"; - break; - - case InputDevice::C_y: - out << "y"; - break; - - case InputDevice::C_trigger: - out << "trigger"; - break; - - case InputDevice::C_throttle: - out << "throttle"; - break; - - case InputDevice::C_twist: - out << "twist"; - break; - - case InputDevice::C_rudder: - out << "rudder"; - break; - - case InputDevice::C_hat_x: - out << "hat_x"; - break; - - case InputDevice::C_hat_y: - out << "hat_y"; - break; - - case InputDevice::C_wheel: - out << "wheel"; - break; - - case InputDevice::C_accelerator: - out << "accelerator"; - break; - - case InputDevice::C_brake: - out << "brake"; - break; - } - + out << InputDevice::format_axis(axis); return out; } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index f4727e0a9c..43dbe14efe 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -78,6 +78,9 @@ PUBLISHED: // Head-mounted display. DC_hmd, + // 3D mouse, such as produced by 3Dconnexion. + DC_3d_mouse, + // Count of this enum, used for loops DC_COUNT, }; @@ -95,6 +98,16 @@ PUBLISHED: enum ControlAxis { C_none, + // Generic translational axes + C_x, + C_y, + C_z, + + // Generic rotational axes, used by joysticks and 3D mice + C_yaw, + C_pitch, + C_roll, + // Gamepad C_left_x, C_left_y, @@ -103,15 +116,9 @@ PUBLISHED: C_right_y, C_right_trigger, - // Flight stick - C_x, - C_y, - C_trigger, + // Flight stick specific C_throttle, - C_twist, - C_rudder, - C_hat_x, - C_hat_y, + C_rudder, // When available separately from yaw // Steering wheel / pedals C_wheel, @@ -183,6 +190,8 @@ PUBLISHED: PT(PointerEventList) get_pointer_events(); virtual void output(ostream &out) const; + static string format_device_class(DeviceClass dc); + static string format_axis(ControlAxis axis); protected: // Called during the constructor to add new controls or buttons diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 8cd612f2d7..9aca81ad3d 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -251,7 +251,7 @@ open_device() { if (_device_class == DC_gamepad) { axis = C_left_trigger; } else { - axis = C_trigger; + //axis = C_trigger; } break; @@ -288,28 +288,34 @@ open_device() { break; case ABS_HAT0X: - if (_dpad_left_button == -1 && _device_class == DC_gamepad) { - // Emulate D-Pad. + if (_dpad_left_button == -1) { + // Emulate D-Pad or hat switch. _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_left())); + _buttons.push_back(ButtonState(GamepadButton::hat_right())); + } axis = C_none; - } else { - axis = C_hat_x; } break; case ABS_HAT0Y: - if (_dpad_up_button == -1 && _device_class == DC_gamepad) { + if (_dpad_up_button == -1) { // Emulate D-Pad. _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_up())); + _buttons.push_back(ButtonState(GamepadButton::hat_down())); + } axis = C_none; - } else { - axis = C_hat_y; } break; @@ -323,7 +329,7 @@ open_device() { } _controls[i].axis = axis; - if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { + if (axis == C_left_trigger || axis == C_right_trigger) { // We'd like to use 0.0 to indicate the resting position. _controls[i]._scale = 1.0 / 65534.0; _controls[i]._bias = 0.5; diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index 6507d4d80a..7bd8203039 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -39,7 +39,7 @@ private: int _fd; int _index; - // These are used for D-pad emulation. + // These are used for D-pad / hat switch emulation. int _dpad_x_axis; int _dpad_y_axis; int _dpad_left_button; diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 04d88996f5..57fa73a416 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -271,10 +271,10 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { case XINPUT_DEVSUBTYPE_FLIGHT_STICK: _device_class = DC_flight_stick; - set_control_map(0, C_rudder); + set_control_map(0, C_yaw); set_control_map(1, C_throttle); - set_control_map(2, C_x); - set_control_map(3, C_y); + set_control_map(2, C_roll); + set_control_map(3, C_pitch); set_control_map(4, C_hat_x); set_control_map(5, C_hat_y); break; diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index 7ddbc33b17..fc580e6b5e 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -48,6 +48,32 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(action_1) DEFINE_GAMEPAD_BUTTON_HANDLE(action_2) DEFINE_GAMEPAD_BUTTON_HANDLE(trigger) +DEFINE_GAMEPAD_BUTTON_HANDLE(hat_up) +DEFINE_GAMEPAD_BUTTON_HANDLE(hat_down) +DEFINE_GAMEPAD_BUTTON_HANDLE(hat_left) +DEFINE_GAMEPAD_BUTTON_HANDLE(hat_right) + +/** + * Returns the ButtonHandle associated with the particular numbered joystick + * button (zero-based), if there is one, or ButtonHandle::none() if there is + * not. + */ +ButtonHandle GamepadButton:: +joystick(int button_number) { + if (button_number >= 0) { + // "button1" does not exist, it is called "trigger" instead + static pvector buttons(1, _trigger); + while (button_number >= buttons.size()) { + char numstr[20]; + sprintf(numstr, "joystick%d", (int)buttons.size() + 1); + ButtonHandle handle; + ButtonRegistry::ptr()->register_button(handle, numstr); + buttons.push_back(handle); + } + return buttons[button_number]; + } + return ButtonHandle::none(); +} /** * This is intended to be called only once, by the static initialization @@ -85,4 +111,8 @@ init_gamepad_buttons() { ButtonRegistry::ptr()->register_button(_action_2, "action_2"); ButtonRegistry::ptr()->register_button(_trigger, "trigger"); + ButtonRegistry::ptr()->register_button(_hat_up, "hat_up"); + ButtonRegistry::ptr()->register_button(_hat_down, "hat_down"); + ButtonRegistry::ptr()->register_button(_hat_left, "hat_left"); + ButtonRegistry::ptr()->register_button(_hat_right, "hat_right"); } diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index 0edbbad99b..768f634380 100644 --- a/panda/src/putil/gamepadButton.h +++ b/panda/src/putil/gamepadButton.h @@ -53,7 +53,13 @@ PUBLISHED: static ButtonHandle action_1(); static ButtonHandle action_2(); + // Flight stick buttons, takes zero-based index. First is always trigger. static ButtonHandle trigger(); + static ButtonHandle joystick(int button_number); + static ButtonHandle hat_up(); + static ButtonHandle hat_down(); + static ButtonHandle hat_left(); + static ButtonHandle hat_right(); public: static void init_gamepad_buttons(); From 9fbdefe12073ef653c5605d3989808ab7eae4203 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 17:26:48 +0100 Subject: [PATCH 44/82] input: add analog trigger emulation on Linux --- panda/src/device/evdevInputDevice.cxx | 29 +++++- panda/src/device/evdevInputDevice.h | 5 + panda/src/device/linuxJoystickDevice.cxx | 125 ++++++----------------- panda/src/device/linuxJoystickDevice.h | 5 + 4 files changed, 68 insertions(+), 96 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 67fa1193c7..c94c71df98 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -90,7 +90,9 @@ EvdevInputDevice(int index) : _dpad_x_axis(-1), _dpad_y_axis(-1), _dpad_left_button(-1), - _dpad_up_button(-1) { + _dpad_up_button(-1), + _ltrigger_code(-1), + _rtrigger_code(-1) { char path[64]; sprintf(path, "/dev/input/event%d", index); @@ -239,6 +241,7 @@ init_device() { bool all_values_zero = true; bool emulate_dpad = true; + bool have_analog_triggers = false; bool has_keys = false; bool has_axes = false; @@ -375,7 +378,12 @@ init_device() { } if (button.handle == GamepadButton::dpad_left()) { emulate_dpad = false; + } else if (button.handle == GamepadButton::ltrigger()) { + _ltrigger_code = i; + } else if (button.handle == GamepadButton::rtrigger()) { + _rtrigger_code = i; } + _buttons.push_back(button); if (i >= _button_indices.size()) { _button_indices.resize(i + 1, -1); @@ -415,6 +423,7 @@ init_device() { axis = InputDevice::C_right_x; } else if (_device_class == DC_gamepad) { axis = InputDevice::C_left_trigger; + have_analog_triggers = true; } else { axis = InputDevice::C_throttle; } @@ -434,6 +443,7 @@ init_device() { axis = InputDevice::C_right_y; } else if (_device_class == DC_gamepad) { axis = InputDevice::C_right_trigger; + have_analog_triggers = true; } else { axis = InputDevice::C_yaw; } @@ -454,6 +464,7 @@ init_device() { case ABS_GAS: if (_device_class == DC_gamepad) { axis = InputDevice::C_right_trigger; + have_analog_triggers = true; } else { axis = InputDevice::C_accelerator; } @@ -461,6 +472,7 @@ init_device() { case ABS_BRAKE: if (_device_class == DC_gamepad) { axis = InputDevice::C_left_trigger; + have_analog_triggers = true; } else { axis = InputDevice::C_brake; } @@ -541,6 +553,16 @@ init_device() { } } + if (_ltrigger_code >= 0 && _rtrigger_code >= 0 && !have_analog_triggers) { + // Emulate analog triggers. + _ltrigger_control = (int)_controls.size(); + add_control(C_left_trigger, 0, 1, false); + add_control(C_right_trigger, 0, 1, false); + } else { + _ltrigger_code = -1; + _rtrigger_code = -1; + } + char path[64]; char buffer[256]; sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); @@ -666,6 +688,11 @@ process_events() { if (index >= 0) { button_changed(index, events[i].value != 0); } + if (code == _ltrigger_code) { + control_changed(_ltrigger_control, events[i].value); + } else if (code == _rtrigger_code) { + control_changed(_ltrigger_control + 1, events[i].value); + } break; default: diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 9ebaef03f2..0a6baa058a 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -53,6 +53,11 @@ private: int _dpad_left_button; int _dpad_up_button; + // This is used for axis emulation. + int _ltrigger_control; + int _ltrigger_code; + int _rtrigger_code; + public: static ButtonHandle map_button(int code, DeviceClass device_class = DC_unknown); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 9aca81ad3d..7c62ce2ae5 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -12,6 +12,7 @@ */ #include "linuxJoystickDevice.h" +#include "evdevInputDevice.h" #ifdef PHAVE_LINUX_INPUT_H @@ -32,7 +33,9 @@ LinuxJoystickDevice(int index) : _dpad_x_axis(-1), _dpad_y_axis(-1), _dpad_left_button(-1), - _dpad_up_button(-1) + _dpad_up_button(-1), + _ltrigger_button(-1), + _rtrigger_button(-1) { LightMutexHolder holder(_lock); if (!open_device()) { @@ -107,6 +110,8 @@ open_device() { ioctl(_fd, JSIOCGNAME(sizeof(name)), name); _name = name; + bool have_analog_triggers = false; + // Get the number of axes. uint8_t num_axes = 0, num_buttons = 0; ioctl(_fd, JSIOCGAXES, &num_axes); @@ -120,104 +125,18 @@ open_device() { ioctl(_fd, JSIOCGBTNMAP, btnmap); for (uint8_t i = 0; i < num_buttons; ++i) { - ButtonHandle handle = ButtonHandle::none(); - switch (btnmap[i]) { - case BTN_A: - handle = GamepadButton::action_a(); - _device_class = DC_gamepad; - break; - - case BTN_B: - handle = GamepadButton::action_b(); - break; - - case BTN_C: - handle = GamepadButton::action_c(); - break; - - case BTN_X: - handle = GamepadButton::action_x(); - break; - - case BTN_Y: - handle = GamepadButton::action_y(); - break; - - case BTN_Z: - handle = GamepadButton::action_z(); - break; - - case BTN_TL: - handle = GamepadButton::lshoulder(); - break; - - case BTN_TR: - handle = GamepadButton::rshoulder(); - break; - - case BTN_TL2: - handle = GamepadButton::ltrigger(); - break; - - case BTN_TR2: - handle = GamepadButton::rtrigger(); - break; - - case BTN_1: - handle = GamepadButton::action_1(); - break; - - case BTN_2: - handle = GamepadButton::action_2(); - break; - - case BTN_SELECT: - case KEY_PREVIOUS: - handle = GamepadButton::back(); - break; - - case BTN_START: - case KEY_NEXT: - handle = GamepadButton::start(); - break; - - case BTN_MODE: - handle = GamepadButton::guide(); - break; - - case BTN_THUMBL: - handle = GamepadButton::lstick(); - break; - - case BTN_THUMBR: - handle = GamepadButton::rstick(); - break; - - case BTN_TRIGGER_HAPPY1: - handle = GamepadButton::dpad_left(); - _dpad_left_button = i; - break; - - case BTN_TRIGGER_HAPPY2: - handle = GamepadButton::dpad_right(); - break; - - case BTN_TRIGGER_HAPPY3: - handle = GamepadButton::dpad_up(); - _dpad_up_button = i; - break; - - case BTN_TRIGGER_HAPPY4: - handle = GamepadButton::dpad_down(); - break; - - default: + ButtonHandle handle = EvdevInputDevice::map_button(btnmap[i]); + if (handle == ButtonHandle::none()) { if (device_cat.is_debug()) { device_cat.debug() << "Unmapped /dev/input/js" << _index << " button " << (int)i << ": 0x" << hex << btnmap[i] << "\n"; } - handle = ButtonHandle::none(); - break; + } else if (handle == GamepadButton::action_a()) { + _device_class = DC_gamepad; + } else if (handle == GamepadButton::ltrigger()) { + _ltrigger_button = i; + } else if (handle == GamepadButton::rtrigger()) { + _rtrigger_button = i; } _buttons[i].handle = handle; } @@ -333,6 +252,7 @@ open_device() { // We'd like to use 0.0 to indicate the resting position. _controls[i]._scale = 1.0 / 65534.0; _controls[i]._bias = 0.5; + have_analog_triggers = true; } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { _controls[i]._scale = 1.0 / -32767.0; _controls[i]._bias = 0.0; @@ -343,6 +263,16 @@ open_device() { } } + if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) { + // Emulate analog triggers. + _ltrigger_control = (int)_controls.size(); + add_control(C_left_trigger, 0, 1, false); + add_control(C_right_trigger, 0, 1, false); + } else { + _ltrigger_button = -1; + _rtrigger_button = -1; + } + // Get additional information from sysfs. sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index); FILE *f = fopen(path, "r"); @@ -454,6 +384,11 @@ process_events() { int index = events[i].number; if (events[i].type & JS_EVENT_BUTTON) { + if (index == _ltrigger_button) { + control_changed(_ltrigger_control, events[i].value); + } else if (index == _rtrigger_button) { + control_changed(_ltrigger_control + 1, events[i].value); + } button_changed(index, (events[i].value != 0)); } else if (events[i].type & JS_EVENT_AXIS) { diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index 7bd8203039..ab97dd5293 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -45,6 +45,11 @@ private: int _dpad_left_button; int _dpad_up_button; + // This is used for axis emulation. + int _ltrigger_control; + int _ltrigger_button; + int _rtrigger_button; + public: static TypeHandle get_class_type() { return _type_handle; From 2523bb032679202d645a285bb8eef5243151c913 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 17:36:52 +0100 Subject: [PATCH 45/82] input: fixes for 3D mouse axes on Linux --- panda/src/device/evdevInputDevice.cxx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index c94c71df98..cf20e57261 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -424,17 +424,23 @@ init_device() { } else if (_device_class == DC_gamepad) { axis = InputDevice::C_left_trigger; have_analog_triggers = true; + } else if (_device_class == DC_3d_mouse) { + axis = InputDevice::C_z; } else { axis = InputDevice::C_throttle; } break; case ABS_RX: - if ((quirks & QB_rstick_from_z) == 0) { + if (_device_class == DC_3d_mouse) { + axis = InputDevice::C_pitch; + } else if ((quirks & QB_rstick_from_z) == 0) { axis = InputDevice::C_right_x; } break; case ABS_RY: - if ((quirks & QB_rstick_from_z) == 0) { + if (_device_class == DC_3d_mouse) { + axis = InputDevice::C_roll; + } else if ((quirks & QB_rstick_from_z) == 0) { axis = InputDevice::C_right_y; } break; @@ -513,7 +519,8 @@ init_device() { // Also reverse the yaw axis to match right-hand coordinate system. // Also T.Flight Hotas X throttle is reversed and can go backwards. if (axis == C_yaw || axis == C_left_y || axis == C_right_y || - (axis == C_throttle && (quirks & QB_reversed_throttle) != 0)) { + (axis == C_throttle && (quirks & QB_reversed_throttle) != 0) || + (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z))) { swap(absinfo.maximum, absinfo.minimum); } if (axis == C_throttle && (quirks & QB_centered_throttle) != 0) { From 96f6bd7fa566532a57a2fb3cc2a0e590c212422c Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 17:39:44 +0100 Subject: [PATCH 46/82] input: fixes for joysticks in Linux compatibility mode --- panda/src/device/linuxJoystickDevice.cxx | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 7c62ce2ae5..f510e02f6a 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -133,6 +133,8 @@ open_device() { } } else if (handle == GamepadButton::action_a()) { _device_class = DC_gamepad; + } else if (handle == GamepadButton::trigger()) { + _device_class = DC_flight_stick; } else if (handle == GamepadButton::ltrigger()) { _ltrigger_button = i; } else if (handle == GamepadButton::rtrigger()) { @@ -152,17 +154,21 @@ open_device() { switch (axmap[i]) { case ABS_X: if (_device_class == DC_gamepad) { - axis = C_left_x; + axis = InputDevice::C_left_x; + } else if (_device_class == DC_flight_stick) { + axis = InputDevice::C_roll; } else { - axis = C_x; + axis = InputDevice::C_x; } break; case ABS_Y: if (_device_class == DC_gamepad) { - axis = C_left_y; + axis = InputDevice::C_left_y; + } else if (_device_class == DC_flight_stick) { + axis = InputDevice::C_pitch; } else { - axis = C_y; + axis = InputDevice::C_y; } break; @@ -183,7 +189,11 @@ open_device() { break; case ABS_RZ: - axis = C_right_trigger; + if (_device_class == DC_gamepad) { + axis = InputDevice::C_right_trigger; + } else { + axis = InputDevice::C_yaw; + } break; case ABS_THROTTLE: From 73b4217cad150548779396cf1e1a497d2b78f255 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 19:34:49 +0100 Subject: [PATCH 47/82] input: fix joystick button and axis mapping on macOS --- panda/src/device/ioKitInputDevice.cxx | 65 +++++++++------------------ panda/src/device/ioKitInputDevice.h | 2 +- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index ec0522b1a0..3b18b3546c 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -100,9 +100,11 @@ IOKitInputDevice(IOHIDDeviceRef device) : CFRelease(elements); if (_hat_element != nullptr) { - _hat_x_axis = _controls.size(); - add_control(C_hat_x, -1, 1, true); - add_control(C_hat_y, -1, 1, true); + _hat_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::hat_left())); + _buttons.push_back(ButtonState(GamepadButton::hat_right())); + _buttons.push_back(ButtonState(GamepadButton::hat_down())); + _buttons.push_back(ButtonState(GamepadButton::hat_up())); } if (_pointer_x != nullptr && _pointer_y != nullptr) { @@ -163,6 +165,8 @@ parse_element(IOHIDElementRef element) { case kHIDUsage_GD_X: if (_device_class == DC_gamepad) { axis = C_left_x; + } else if (_device_class == DC_flight_stick) { + axis = C_roll; } else if (_device_class == DC_mouse) { _pointer_x = element; return; @@ -173,6 +177,8 @@ parse_element(IOHIDElementRef element) { case kHIDUsage_GD_Y: if (_device_class == DC_gamepad) { axis = C_left_y; + } else if (_device_class == DC_flight_stick) { + axis = C_pitch; } else if (_device_class == DC_mouse) { _pointer_y = element; return; @@ -197,7 +203,7 @@ parse_element(IOHIDElementRef element) { if (_device_class == DC_gamepad) { axis = C_right_trigger; } else { - axis = C_twist; + axis = C_yaw; } break; case kHIDUsage_GD_Slider: @@ -253,8 +259,9 @@ parse_element(IOHIDElementRef element) { if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. add_control(axis, max, min, true); - } else if (axis == C_y || axis == C_left_y || axis == C_right_y) { + } else if (axis == C_yaw || axis == C_left_y || axis == C_right_y) { // We'd like to reverse the Y axis to match the XInput behavior. + // We also reverse yaw to obey the right-hand rule. add_control(axis, max, min); } else { add_control(axis, min, max); @@ -614,6 +621,10 @@ parse_element(IOHIDElementRef element) { if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { handle = gamepad_buttons[usage]; } + } else if (_device_class == DC_flight_stick) { + if (usage > 0) { + handle = GamepadButton::joystick(usage - 1); + } } else if (_device_class == DC_mouse) { // In Panda, wheel and right button are flipped around... int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); @@ -681,46 +692,10 @@ do_poll() { IOHIDValueRef value_ref; if (IOHIDDeviceGetValue(_device, _hat_element, &value_ref) == kIOReturnSuccess) { int value = IOHIDValueGetIntegerValue(value_ref); - int x = 0; - int y = 0; - switch (value) { - case 0: - x = 0; - y = -1; - break; - case 1: - x = 1; - y = -1; - break; - case 2: - x = 1; - y = 0; - break; - case 3: - x = 1; - y = 1; - break; - case 4: - x = 0; - y = 1; - break; - case 5: - x = -1; - y = 1; - break; - case 6: - x = -1; - y = 0; - break; - case 7: - x = -1; - y = -1; - break; - default: - break; - } - control_changed(_hat_x_axis, x); - control_changed(_hat_x_axis + 1, y); + button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left + button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right + button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down + button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up } } diff --git a/panda/src/device/ioKitInputDevice.h b/panda/src/device/ioKitInputDevice.h index 1dfde3d034..5adc0205f7 100644 --- a/panda/src/device/ioKitInputDevice.h +++ b/panda/src/device/ioKitInputDevice.h @@ -41,7 +41,7 @@ private: pvector _button_elements; pvector _analog_elements; IOHIDElementRef _hat_element; - int _hat_x_axis; + int _hat_left_button; IOHIDElementRef _pointer_x; IOHIDElementRef _pointer_y; IOHIDElementRef _scroll_wheel; From 8bd617c4eebe1dc517d7433190a2a9e47c34be23 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 22 Jan 2018 21:58:26 +0100 Subject: [PATCH 48/82] input: new windows input manager based on raw input The new implementation uses a message-only window to register raw input events. XInput is still used when possible, but raw input is being used to handle device detection. (XInput is only enabled when an XInput device is plugged in, which also prevents us from having to load and poll the XInput library unnecessarily.) For raw devices, the Windows HID parser library is used to parse the raw data. Unfortunately, the Windows 7.1 SDK does not ship hid.lib (which is in the WDK 7.1.0), so I am dynamically loading hid.dll to prevent pulling in a dependency on the WDK. (But perhaps we can ship hid.lib in the thirdparty libraries?) Input devices other than XInput gamepads and the 3D mouse have not been tested very well yet. Adding keyboard and mouse support is still a TODO. This also splits out the Windows implementation of InputDeviceManager into a separate subclass; it is intended that the other implementations will follow suit. --- dtool/src/parser-inc/XInput.h | 8 +- panda/src/device/inputDevice.cxx | 7 +- panda/src/device/inputDeviceManager.I | 8 +- panda/src/device/inputDeviceManager.cxx | 68 +-- panda/src/device/inputDeviceManager.h | 19 +- panda/src/device/inputDeviceNode.cxx | 4 +- panda/src/device/p3device_composite1.cxx | 2 + panda/src/device/winInputDeviceManager.cxx | 384 ++++++++++++ panda/src/device/winInputDeviceManager.h | 64 ++ panda/src/device/winRawInputDevice.cxx | 655 +++++++++++++++++++++ panda/src/device/winRawInputDevice.h | 85 +++ panda/src/device/xInputDevice.cxx | 208 ++++--- panda/src/device/xInputDevice.h | 12 +- 13 files changed, 1350 insertions(+), 174 deletions(-) create mode 100644 panda/src/device/winInputDeviceManager.cxx create mode 100644 panda/src/device/winInputDeviceManager.h create mode 100644 panda/src/device/winRawInputDevice.cxx create mode 100644 panda/src/device/winRawInputDevice.h diff --git a/dtool/src/parser-inc/XInput.h b/dtool/src/parser-inc/XInput.h index 21a2802287..0674961fc0 100644 --- a/dtool/src/parser-inc/XInput.h +++ b/dtool/src/parser-inc/XInput.h @@ -1,3 +1,5 @@ -struct XINPUT_STATE; -struct XINPUT_CAPABILITIES; -struct XINPUT_VIBRATION; +typedef struct _XINPUT_STATE XINPUT_STATE, *PXINPUT_STATE; +typedef struct _XINPUT_CAPABILITIES XINPUT_CAPABILITIES, *PXINPUT_CAPABILITIES; +typedef struct _XINPUT_VIBRATION XINPUT_VIBRATION, *PXINPUT_VIBRATION; +typedef struct _XINPUT_GAMEPAD XINPUT_GAMEPAD, *PXINPUT_GAMEPAD; +typedef struct _XINPUT_KEYSTROKE XINPUT_KEYSTROKE, *PXINPUT_KEYSTROKE; diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 76aa79dc58..1aa2a03ca4 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -13,6 +13,10 @@ #include "inputDevice.h" +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define fma(a, b, c) ((a) * (b) + (c)) +#endif + TypeHandle InputDevice::_type_handle; /** @@ -29,7 +33,8 @@ InputDevice(const string &name, DeviceClass dev_class, int flags) : _event_sequence(0), _enable_pointer_events(false), _battery_level(-1), - _max_battery_level(-1) + _max_battery_level(-1), + _lock("InputDevice") { _button_events = new ButtonEventList; } diff --git a/panda/src/device/inputDeviceManager.I b/panda/src/device/inputDeviceManager.I index b38dc6d2d5..731051aa3c 100644 --- a/panda/src/device/inputDeviceManager.I +++ b/panda/src/device/inputDeviceManager.I @@ -16,8 +16,10 @@ */ INLINE InputDeviceManager *InputDeviceManager:: get_global_ptr() { - if (_global_ptr == (InputDeviceManager *)NULL) { - _global_ptr = new InputDeviceManager; + if (_global_ptr != nullptr) { + return _global_ptr; + } else { + make_global_ptr(); + return _global_ptr; } - return _global_ptr; } diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index d7f6a04955..31f7f93934 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -14,6 +14,7 @@ #include "inputDeviceManager.h" #include "ioKitInputDevice.h" #include "linuxJoystickDevice.h" +#include "winInputDeviceManager.h" #include "throw_event.h" #ifdef PHAVE_LINUX_INPUT_H @@ -23,12 +24,6 @@ #include #endif -static ConfigVariableDouble xinput_detection_delay -("xinput-detection-delay", 0.5, - PRC_DESC("How many seconds to wait between each check to see whether " - "an XInput has been connected. This check is not done every " - "frame in order to prevent slowdown.")); - InputDeviceManager *InputDeviceManager::_global_ptr = NULL; /** @@ -80,35 +75,6 @@ InputDeviceManager() : _inotify_fd(-1) { return; } } -#elif defined(_WIN32) -InputDeviceManager:: -InputDeviceManager() : - _xinput_device0(0), - _xinput_device1(1), - _xinput_device2(2), - _xinput_device3(3), - _last_detection(0.0) { - - // XInput provides four device slots, so we simply create four XInputDevice - // objects that are bound to the lifetime of the input manager. - _xinput_device0.local_object(); - _xinput_device1.local_object(); - _xinput_device2.local_object(); - _xinput_device3.local_object(); - - if (_xinput_device0.is_connected()) { - _connected_devices.add_device(&_xinput_device0); - } - if (_xinput_device1.is_connected()) { - _connected_devices.add_device(&_xinput_device1); - } - if (_xinput_device2.is_connected()) { - _connected_devices.add_device(&_xinput_device2); - } - if (_xinput_device3.is_connected()) { - _connected_devices.add_device(&_xinput_device3); - } -} #elif defined(__APPLE__) InputDeviceManager:: InputDeviceManager() { @@ -153,7 +119,7 @@ InputDeviceManager() { } #else InputDeviceManager:: -InputDeviceManager() { +InputDeviceManager() : _lock("InputDeviceManager") { } #endif @@ -174,6 +140,18 @@ InputDeviceManager:: #endif } +/** + * Creates the global input manager. + */ +void InputDeviceManager:: +make_global_ptr() { +#ifdef _WIN32 + _global_ptr = new WinInputDeviceManager; +#else + _global_ptr = new InputDeviceManager; +#endif +} + #ifdef PHAVE_LINUX_INPUT_H /** * Checks whether the event device with the given index is accessible, and if @@ -455,24 +433,6 @@ update() { ptr += sizeof(inotify_event) + event->len; } #endif - -#ifdef _WIN32 - // XInput doesn't provide a very good hot-plugging interface. We just check - // whether it's connected every so often. Perhaps we can switch to using - // RegisterDeviceNotification in the future. - double time_now = ClockObject::get_global_clock()->get_real_time(); - LightMutexHolder holder(_update_lock); - - if (time_now - _last_detection > xinput_detection_delay.get_value()) { - // I've heard this can be quite slow if no device is detected. We - // should probably move it to a thread. - _xinput_device0.detect(this); - _xinput_device1.detect(this); - _xinput_device2.detect(this); - _xinput_device3.detect(this); - _last_detection = time_now; - } -#endif } #if defined(__APPLE__) && !defined(CPPPARSER) diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index a262b60d28..c6c50a33d9 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -21,6 +21,7 @@ #ifdef _WIN32 #include "xinputDevice.h" +class WinRawInputDevice; #endif #ifdef __APPLE__ @@ -32,10 +33,12 @@ * when a device has been hot-plugged. */ class EXPCL_PANDA_DEVICE InputDeviceManager { -private: +protected: InputDeviceManager(); ~InputDeviceManager(); + static void make_global_ptr(); + #ifdef PHAVE_LINUX_INPUT_H InputDevice *consider_add_evdev_device(int index); InputDevice *consider_add_js_device(int index); @@ -48,14 +51,14 @@ PUBLISHED: void add_device(InputDevice *device); void remove_device(InputDevice *device); - void update(); + virtual void update(); INLINE static InputDeviceManager *get_global_ptr(); // The set of all currently connected devices. MAKE_PROPERTY(devices, get_devices); -private: +protected: LightMutex _lock; #ifdef PHAVE_LINUX_INPUT_H @@ -65,16 +68,6 @@ private: InputDeviceSet _inactive_devices; #endif -#ifdef _WIN32 - // There are always exactly four of these in existence. - LightMutex _update_lock; - XInputDevice _xinput_device0; - XInputDevice _xinput_device1; - XInputDevice _xinput_device2; - XInputDevice _xinput_device3; - double _last_detection; -#endif - #if defined(__APPLE__) && !defined(CPPPARSER) IOHIDManagerRef _hid_manager; diff --git a/panda/src/device/inputDeviceNode.cxx b/panda/src/device/inputDeviceNode.cxx index 662b2dbac8..e68fd8652b 100644 --- a/panda/src/device/inputDeviceNode.cxx +++ b/panda/src/device/inputDeviceNode.cxx @@ -61,11 +61,11 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, } // calculate the battery level in percent and set a warning if the level is to low - if (_device->has_battery()) { + /*if (_device->has_battery()) { short bl = _device->get_battery_level(); short bl_percent = bl / (_device->get_max_battery_level() / (short)100); if (bl_percent <= low_battery_level) { output.set_data(_low_battery_event_output, EventParameter(1)); } - } + }*/ } diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index b9895f7880..65a03ccc48 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -12,3 +12,5 @@ #include "inputDeviceSet.cxx" #include "ioKitInputDevice.cxx" #include "linuxJoystickDevice.cxx" +#include "winInputDeviceManager.cxx" +#include "winRawInputDevice.cxx" diff --git a/panda/src/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx new file mode 100644 index 0000000000..124229b5ea --- /dev/null +++ b/panda/src/device/winInputDeviceManager.cxx @@ -0,0 +1,384 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winInputDeviceManager.cxx + * @author rdb + * @date 2018-01-21 + */ + +#include "winInputDeviceManager.h" +#include "winRawInputDevice.h" +#include "throw_event.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +/** + * Initializes the input device manager by scanning which devices are currently + * connected and setting up any platform-dependent structures necessary for + * listening for future device connect events. + */ +WinInputDeviceManager:: +WinInputDeviceManager() : + _xinput_device0(0), + _xinput_device1(1), + _xinput_device2(2), + _xinput_device3(3), + _message_hwnd(nullptr) { + + // XInput provides four device slots, so we simply create four XInputDevice + // objects that are bound to the lifetime of the input manager. + _xinput_device0.local_object(); + _xinput_device1.local_object(); + _xinput_device2.local_object(); + _xinput_device3.local_object(); + +// This function is only available in Vista and later, so we use a wrapper. + HMODULE module = LoadLibraryA("cfgmgr32.dll"); + if (module) { + _CM_Get_DevNode_PropertyW = (pCM_Get_DevNode_Property)GetProcAddress(module, "CM_Get_DevNode_PropertyW"); + } else { + _CM_Get_DevNode_PropertyW = nullptr; + } + + // Now create a message-only window for the raw input. + WNDCLASSEX wc = {}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.lpfnWndProc = window_proc; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpszClassName = "InputDeviceManager"; + if (!RegisterClassEx(&wc)) { + device_cat.warning() + << "Failed to register message-only window class.\n"; + return; + } + + _message_hwnd = CreateWindowEx(0, wc.lpszClassName, "InputDeviceManager", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr); + if (!_message_hwnd) { + device_cat.warning() + << "Failed to create message-only window.\n"; + return; + } + + // Now listen for raw input devices using the created message loop. + RAWINPUTDEVICE rid[3]; + rid[0].usUsagePage = 1; + rid[0].usUsage = 4; // Joysticks + rid[0].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; + rid[0].hwndTarget = _message_hwnd; + rid[1].usUsagePage = 1; + rid[1].usUsage = 5; // Gamepads + rid[1].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; + rid[1].hwndTarget = _message_hwnd; + rid[2].usUsagePage = 1; + rid[2].usUsage = 8; // Multi-axis controllers (including 3D mice) + rid[2].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; + rid[2].hwndTarget = _message_hwnd; + if (!RegisterRawInputDevices(rid, 3, sizeof(RAWINPUTDEVICE))) { + device_cat.warning() + << "Failed to register raw input devices.\n"; + } +} + +/** + * Closes any resources that the device manager was using to listen for events. + */ +WinInputDeviceManager:: +~WinInputDeviceManager() { + if (_message_hwnd != nullptr) { + DestroyWindow(_message_hwnd); + _message_hwnd = nullptr; + } +} + +/** + * Called by the raw input device destructor. + */ +void WinInputDeviceManager:: +device_destroyed(WinRawInputDevice *device) { + LightMutexHolder holder(_lock); + // It shouldn't be in here, but let's check to be sure. + if (device->_handle != nullptr) { + _raw_devices.erase(device->_handle); + } + + _raw_devices_by_path.erase(device->_path); +} + +/** + * Called upon receiving a WM_INPUT message. + */ +void WinInputDeviceManager:: +on_input(HRAWINPUT handle) { + UINT size; + if (GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)) < 0) { + return; + } + + PRAWINPUT data = (PRAWINPUT)alloca(size); + if (GetRawInputData(handle, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER)) <= 0) { + return; + } + + // Look up the device in the map. + PT(WinRawInputDevice) device; + { + LightMutexHolder holder(_lock); + auto it = _raw_devices.find(data->header.hDevice); + if (it != _raw_devices.end()) { + device = it->second; + } + } + if (device != nullptr) { + device->on_input(data); + } +} + +/** + * Called upon receiving WM_INPUT_DEVICE_CHANGE with wparam GIDC_ARRIVAL. + */ +void WinInputDeviceManager:: +on_input_device_arrival(HANDLE handle) { + // Get the device path. + UINT size; + if (GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, nullptr, &size) != 0) { + return; + } + + char *path = (char *)alloca(size); + if (path == nullptr || + GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, (void *)path, &size) < 0) { + return; + } + + if (device_cat.is_debug()) { + device_cat.debug() + << "GIDC_ARRIVAL: " << path << "\n"; + } + + // Get the device info. + RID_DEVICE_INFO info; + info.cbSize = sizeof(RID_DEVICE_INFO); + size = sizeof(RID_DEVICE_INFO); + if (GetRawInputDeviceInfoA(handle, RIDI_DEVICEINFO, &info, &size) <= 0) { + return; + } + + // Strip the \\?\ prefix from the path. + while (path[0] == '\\' || path[0] == '?' || path[0] == '.') { + ++path; + } + + // Now, replace # with \\ in the path, but only up to three components. + char *p = path; + int i = 0; + while (*p != '\0') { + if (*p == '#') { + if (i++ < 2) { + *p = '\\'; + } else { + *p = '\0'; + break; + } + } + ++p; + } + + // Find the device node, which will be something like "HID\VID_0123..." + // Then we walk the device tree upward to get the USB node, which which will + // be something like a "USB\VID..." node, from which we can fetch the real + // USB device information (such as the product name). + string name, manufacturer; + DEVINST inst; + CONFIGRET ret = CM_Locate_DevNodeA(&inst, (DEVINSTID_A)path, CM_LOCATE_DEVNODE_PHANTOM); + if (ret == CR_SUCCESS) { + char buffer[4096]; + ULONG buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) { + name.assign(buffer); + } + buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) { + if (strcmp(buffer, "(Standard system devices)") != 0) { + manufacturer.assign(buffer); + } + } + + // Now walk the device tree upwards so fetch the bus-reported name of the + // parent USB device, which we prefer over the regular device description + // that is probably boring like "HID-compliant game controller". + DEVINST cur = inst; + DEVINST parent; + while (CM_Get_Parent(&parent, cur, 0) == CR_SUCCESS) { + buflen = 4096; + string dev_class; + if (CM_Get_DevNode_Registry_Property(parent, CM_DRP_CLASS, 0, buffer, &buflen, 0) == CR_SUCCESS) { + if (strcmp(buffer, "USB") == 0) { + // This is some generic USB device, like a hub. We've gone too far. + break; + } + dev_class.assign(buffer); + } + cur = parent; + + // While we're at it, maybe this one defines a manufacturer? + buflen = 4096; + if (manufacturer.empty() && + CM_Get_DevNode_Registry_Property(cur, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) { + if (strcmp(buffer, "(Standard system devices)") != 0) { + manufacturer.assign(buffer); + } + } + + // If it's a generic HID device, take the name from the USB bus. + // See devpkey.h for the available property keys. + static const DEVPROPKEY bus_reported_device_desc = { + {0x540b947e, 0x8b40, 0x45bc, {0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2}}, + 4, + }; + DEVPROPTYPE type; + buflen = 4096; + if (dev_class == "HIDClass" && _CM_Get_DevNode_PropertyW != nullptr && + _CM_Get_DevNode_PropertyW(cur, &bus_reported_device_desc, &type, (PBYTE)buffer, &buflen, 0) == CR_SUCCESS && + type == DEVPROP_TYPE_STRING) { + TextEncoder encoder; + name.assign(encoder.encode_wtext((wchar_t *)buffer)); + break; + } else { + buflen = 4096; + if (CM_Get_DevNode_Registry_Property(cur, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) { + // We'll pass if it has this awfully boring name. Is there a + // language-independent way to check this? + if (strcmp(buffer, "USB Input Device") != 0) { + name.assign(buffer); + } + } + } + } + } else if (device_cat.is_debug()) { + // No big deal, we just won't be able to get the name. + device_cat.debug() + << "Could not locate device node " << path << " (" << ret << ")\n"; + } + + // Is this an XInput device? If so, handle it via XInput, which allows us + // to handle independent left/right triggers as well as vibration output. + if (info.dwType == RIM_TYPEHID && strstr(path, "&IG_") != nullptr) { + // This is a device we should handle via the XInput API. Check which of + // the four players was the lucky one. + WinRawInputDevice idev(this, path); + if (_xinput_device0.check_arrival(info, inst, name, manufacturer)) { + add_device(&_xinput_device0); + } + if (_xinput_device1.check_arrival(info, inst, name, manufacturer)) { + add_device(&_xinput_device1); + } + if (_xinput_device2.check_arrival(info, inst, name, manufacturer)) { + add_device(&_xinput_device2); + } + if (_xinput_device3.check_arrival(info, inst, name, manufacturer)) { + add_device(&_xinput_device3); + } + return; + } + + LightMutexHolder holder(_lock); + + // Do we have a device by this path already? This can happen if the + // user keeps around a pointer to a disconnected device in the hope that + // it will reconnect later. + PT(WinRawInputDevice) device; + auto it = _raw_devices_by_path.find(path); + if (it != _raw_devices_by_path.end()) { + device = it->second; + } else { + device = new WinRawInputDevice(this, path); + _raw_devices_by_path[path] = device; + } + + if (device->on_arrival(handle, info, move(name))) { + _raw_devices[handle] = device; + _connected_devices.add_device(device); + + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered input device " << *device << "\n"; + } + throw_event("connect-device", device.p()); + } +} + +/** + * Called upon receiving WM_INPUT_DEVICE_CHANGE with wparam GIDC_REMOVAL. + */ +void WinInputDeviceManager:: +on_input_device_removal(HANDLE handle) { + // The handle will probably no longer be valid after this, so find the + // device and remove it from _raw_devices. However, we keep it in + // _raw_devices_by_path in case there's still a pointer around to it. + + // We keep the pointer outside the lock because the input device + // destructor calls back to InputDeviceManager. + PT(WinRawInputDevice) device; + { + LightMutexHolder holder(_lock); + auto it = _raw_devices.find(handle); + if (it != _raw_devices.end()) { + device = move(it->second); + _raw_devices.erase(it); + device->on_removal(); + + if (_connected_devices.remove_device(device)) { + throw_event("disconnect-device", device.p()); + } + if (device_cat.is_debug()) { + device_cat.debug() + << "Removed input device " << *device << "\n"; + } + } + } +} + +/** + * Implementation of the message loop. + */ +LRESULT WINAPI WinInputDeviceManager:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinInputDeviceManager *mgr; + switch (msg) { + case WM_INPUT: + mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr(); + if (mgr != nullptr) { + mgr->on_input((HRAWINPUT)lparam); + } + break; + + case WM_INPUT_DEVICE_CHANGE: + switch (LOWORD(wparam)) { + case GIDC_ARRIVAL: + mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr(); + if (mgr != nullptr) { + mgr->on_input_device_arrival((HANDLE)lparam); + } + break; + + case GIDC_REMOVAL: + mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr(); + if (mgr != nullptr) { + mgr->on_input_device_removal((HANDLE)lparam); + } + break; + } + break; + + default: + break; + } + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +#endif diff --git a/panda/src/device/winInputDeviceManager.h b/panda/src/device/winInputDeviceManager.h new file mode 100644 index 0000000000..de2b0dc04e --- /dev/null +++ b/panda/src/device/winInputDeviceManager.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winInputDeviceManager.h + * @author rdb + * @date 2018-01-21 + */ + +#ifndef WININPUTDEVICEMANAGER_H +#define WININPUTDEVICEMANAGER_H + +#include "inputDeviceManager.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +#include "xinputDevice.h" + +#include +#include + +class WinRawInputDevice; + +/** + * This is the Windows implementation of InputDeviceManager, managing both + * XInput controllers and raw input devices. + */ +class EXPCL_PANDA_DEVICE WinInputDeviceManager FINAL : public InputDeviceManager { +private: + WinInputDeviceManager(); + ~WinInputDeviceManager(); + +public: + void device_destroyed(WinRawInputDevice *device); + + void on_input(HRAWINPUT handle); + void on_input_device_arrival(HANDLE handle); + void on_input_device_removal(HANDLE handle); + +private: + // There are always exactly four of these in existence. + XInputDevice _xinput_device0; + XInputDevice _xinput_device1; + XInputDevice _xinput_device2; + XInputDevice _xinput_device3; + + HWND _message_hwnd; + pmap _raw_devices; + pmap _raw_devices_by_path; + + static LRESULT WINAPI window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + typedef CONFIGRET (*pCM_Get_DevNode_Property)(DEVINST, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE, PULONG, ULONG); + pCM_Get_DevNode_Property _CM_Get_DevNode_PropertyW; + + friend class InputDeviceManager; +}; + +#endif +#endif diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx new file mode 100644 index 0000000000..68e8e89bf3 --- /dev/null +++ b/panda/src/device/winRawInputDevice.cxx @@ -0,0 +1,655 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winRawInputDevice.cxx + * @author rdb + * @date 2018-01-19 + */ + +#include "winRawInputDevice.h" +#include "gamepadButton.h" +#include "mouseButton.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +#include +#include + +// Copy definitions from hidusage.h, until we can drop support for the 7.1 SDK +typedef USHORT USAGE, *PUSAGE; + +#define HID_USAGE_PAGE_UNDEFINED ((USAGE) 0x00) +#define HID_USAGE_PAGE_GENERIC ((USAGE) 0x01) +#define HID_USAGE_PAGE_SIMULATION ((USAGE) 0x02) +#define HID_USAGE_PAGE_VR ((USAGE) 0x03) +#define HID_USAGE_PAGE_SPORT ((USAGE) 0x04) +#define HID_USAGE_PAGE_GAME ((USAGE) 0x05) +#define HID_USAGE_PAGE_KEYBOARD ((USAGE) 0x07) +#define HID_USAGE_PAGE_LED ((USAGE) 0x08) +#define HID_USAGE_PAGE_BUTTON ((USAGE) 0x09) + +#define HID_USAGE_GENERIC_POINTER ((USAGE) 0x01) +#define HID_USAGE_GENERIC_MOUSE ((USAGE) 0x02) +#define HID_USAGE_GENERIC_JOYSTICK ((USAGE) 0x04) +#define HID_USAGE_GENERIC_GAMEPAD ((USAGE) 0x05) +#define HID_USAGE_GENERIC_KEYBOARD ((USAGE) 0x06) +#define HID_USAGE_GENERIC_KEYPAD ((USAGE) 0x07) +#define HID_USAGE_GENERIC_SYSTEM_CTL ((USAGE) 0x80) + +#define HID_USAGE_GENERIC_X ((USAGE) 0x30) +#define HID_USAGE_GENERIC_Y ((USAGE) 0x31) +#define HID_USAGE_GENERIC_Z ((USAGE) 0x32) +#define HID_USAGE_GENERIC_RX ((USAGE) 0x33) +#define HID_USAGE_GENERIC_RY ((USAGE) 0x34) +#define HID_USAGE_GENERIC_RZ ((USAGE) 0x35) +#define HID_USAGE_GENERIC_SLIDER ((USAGE) 0x36) +#define HID_USAGE_GENERIC_DIAL ((USAGE) 0x37) +#define HID_USAGE_GENERIC_WHEEL ((USAGE) 0x38) +#define HID_USAGE_GENERIC_HATSWITCH ((USAGE) 0x39) + +// Copy definitions from hidpi.h, until we can drop support for the 7.1 SDK +#define HIDP_STATUS_SUCCESS ((NTSTATUS)(0x11 << 16)) + +typedef enum _HIDP_REPORT_TYPE { + HidP_Input, + HidP_Output, + HidP_Feature +} HIDP_REPORT_TYPE; + +typedef struct _HIDP_BUTTON_CAPS { + USAGE UsagePage; + UCHAR ReportID; + BOOLEAN IsAlias; + USHORT BitField; + USHORT LinkCollection; + USAGE LinkUsage; + USAGE LinkUsagePage; + BOOLEAN IsRange; + BOOLEAN IsStringRange; + BOOLEAN IsDesignatorRange; + BOOLEAN IsAbsolute; + ULONG Reserved[10]; + union { + struct { + USAGE UsageMin, UsageMax; + USHORT StringMin, StringMax; + USHORT DesignatorMin, DesignatorMax; + USHORT DataIndexMin, DataIndexMax; + } Range; + struct { + USAGE Usage, Reserved1; + USHORT StringIndex, Reserved2; + USHORT DesignatorIndex, Reserved3; + USHORT DataIndex, Reserved4; + } NotRange; + }; +} HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS; + +typedef struct _HIDP_VALUE_CAPS { + USAGE UsagePage; + UCHAR ReportID; + BOOLEAN IsAlias; + USHORT BitField; + USHORT LinkCollection; + USAGE LinkUsage; + USAGE LinkUsagePage; + BOOLEAN IsRange; + BOOLEAN IsStringRange; + BOOLEAN IsDesignatorRange; + BOOLEAN IsAbsolute; + BOOLEAN HasNull; + UCHAR Reserved; + USHORT BitSize; + USHORT ReportCount; + USHORT Reserved2[5]; + ULONG UnitsExp; + ULONG Units; + LONG LogicalMin, LogicalMax; + LONG PhysicalMin, PhysicalMax; + union { + struct { + USAGE UsageMin, UsageMax; + USHORT StringMin, StringMax; + USHORT DesignatorMin, DesignatorMax; + USHORT DataIndexMin, DataIndexMax; + } Range; + struct { + USAGE Usage, Reserved1; + USHORT StringIndex, Reserved2; + USHORT DesignatorIndex, Reserved3; + USHORT DataIndex, Reserved4; + } NotRange; + }; +} HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS; + +typedef PUCHAR PHIDP_REPORT_DESCRIPTOR; +typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA; + +typedef struct _HIDP_CAPS { + USAGE Usage; + USAGE UsagePage; + USHORT InputReportByteLength; + USHORT OutputReportByteLength; + USHORT FeatureReportByteLength; + USHORT Reserved[17]; + USHORT NumberLinkCollectionNodes; + USHORT NumberInputButtonCaps; + USHORT NumberInputValueCaps; + USHORT NumberInputDataIndices; + USHORT NumberOutputButtonCaps; + USHORT NumberOutputValueCaps; + USHORT NumberOutputDataIndices; + USHORT NumberFeatureButtonCaps; + USHORT NumberFeatureValueCaps; + USHORT NumberFeatureDataIndices; +} HIDP_CAPS, *PHIDP_CAPS; + +typedef struct _HIDP_DATA { + USHORT DataIndex; + USHORT Reserved; + union { + ULONG RawValue; + BOOLEAN On; + }; +} HIDP_DATA, *PHIDP_DATA; + +typedef LONG NTSTATUS; +typedef NTSTATUS (*pHidP_GetCaps)(PHIDP_PREPARSED_DATA, PHIDP_CAPS); +typedef NTSTATUS (*pHidP_GetButtonCaps)(HIDP_REPORT_TYPE, PHIDP_BUTTON_CAPS, PUSHORT, PHIDP_PREPARSED_DATA); +typedef NTSTATUS (*pHidP_GetValueCaps)(HIDP_REPORT_TYPE, PHIDP_VALUE_CAPS, PUSHORT, PHIDP_PREPARSED_DATA); +typedef NTSTATUS (*pHidP_GetData)(HIDP_REPORT_TYPE, PHIDP_DATA, PULONG, PHIDP_PREPARSED_DATA, PCHAR, ULONG); +typedef ULONG (*pHidP_MaxDataListLength)(HIDP_REPORT_TYPE, PHIDP_PREPARSED_DATA); + +static pHidP_GetCaps _HidP_GetCaps = nullptr; +static pHidP_GetButtonCaps _HidP_GetButtonCaps = nullptr; +static pHidP_GetValueCaps _HidP_GetValueCaps = nullptr; +static pHidP_GetData _HidP_GetData = nullptr; +static pHidP_MaxDataListLength _HidP_MaxDataListLength = nullptr; + +/** + * Static method to initialize the HID parser library. We load it dynamically + * because the Windows 7.1 SDK doesn't ship hid.lib. + */ +static bool init_hidp() { + HMODULE module = LoadLibraryA("hid.dll"); + if (module) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Successfully loaded hid.dll\n"; + } + + _HidP_GetCaps = (pHidP_GetCaps)GetProcAddress(module, "HidP_GetCaps"); + _HidP_GetButtonCaps = (pHidP_GetButtonCaps)GetProcAddress(module, "HidP_GetButtonCaps"); + _HidP_GetValueCaps = (pHidP_GetValueCaps)GetProcAddress(module, "HidP_GetValueCaps"); + _HidP_GetData = (pHidP_GetData)GetProcAddress(module, "HidP_GetData"); + _HidP_MaxDataListLength = (pHidP_MaxDataListLength)GetProcAddress(module, "HidP_MaxDataListLength"); + + if (_HidP_GetCaps == nullptr || _HidP_GetButtonCaps == nullptr || + _HidP_GetValueCaps == nullptr || _HidP_GetData == nullptr || + _HidP_MaxDataListLength == nullptr) { + device_cat.error() + << "Failed to locate function pointers in hid.dll\n"; + return false; + } + + return true; + } + + device_cat.error() + << "Failed to load hid.dll.\n"; + return false; +} + +/** + * Protected constructor. Given a raw device handle. + */ +WinRawInputDevice:: +WinRawInputDevice(WinInputDeviceManager *manager, const char *path) : + _manager(manager), + _path(path), + _max_data_count(0), + _preparsed(nullptr) { +} + +/** + * + */ +WinRawInputDevice:: +~WinRawInputDevice() { + // Unregister the device from the manager. + LightMutexHolder holder(_lock); + if (_manager != nullptr) { + _manager->device_destroyed(this); + } + if (_preparsed != nullptr) { + free(_preparsed); + _preparsed = nullptr; + } +} + +/** + * Called by InputDeviceManager when this device is connected. Returns true + * if the device was connected successfully. + */ +bool WinRawInputDevice:: +on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name) { + LightMutexHolder holder(_lock); + + _name = move(name); + + switch (info.dwType) { + case RIM_TYPEMOUSE: + _device_class = DC_mouse; + break; + + case RIM_TYPEKEYBOARD: + _device_class = DC_keyboard; + break; + + case RIM_TYPEHID: + _vendor_id = info.hid.dwVendorId; + _product_id = info.hid.dwProductId; + + // Gamepads + if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_GAMEPAD) { + _device_class = DC_gamepad; + + // Flight sticks + } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) { + _device_class = DC_flight_stick; + + // Mice + } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) { + _device_class = DC_mouse; + + // Keyboards + } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_KEYBOARD) { + _device_class = DC_keyboard; + + // 3Dconnexion SpaceNavigator and friends. + } else if (_vendor_id == 0x046d && + (_product_id == 0xc623 || + _product_id == 0xc625 || + _product_id == 0xc626 || + _product_id == 0xc627 || + _product_id == 0xc628 || + _product_id == 0xc629 || + _product_id == 0xc62b)) { + _device_class = DC_3d_mouse; + } + break; + + default: + return false; + } + + // Initialize hid.dll, which provides the HID parser functions. + static bool hid_initialized = false; + if (!hid_initialized) { + if (!init_hidp()) { + return false; + } + hid_initialized = true; + } + + // Get the "preparsed data", which we can parse with the HID parser API. + UINT size = 0; + if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) < 0) { + return false; + } + + PHIDP_PREPARSED_DATA buffer = (PHIDP_PREPARSED_DATA)malloc(size); + if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, buffer, &size) <= 0) { + return false; + } + _preparsed = buffer; + + HIDP_CAPS caps; + if (_HidP_GetCaps(buffer, &caps) != HIDP_STATUS_SUCCESS) { + device_cat.warning() + << "Failed to get capabilities from HID preparsed data.\n"; + return false; + } + + // Prepare a mapping of data indices to button/control indices. + _indices.resize(caps.NumberInputDataIndices); + + _buttons.clear(); + _controls.clear(); + + USHORT num_button_caps = caps.NumberInputButtonCaps; + PHIDP_BUTTON_CAPS button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS)); + _HidP_GetButtonCaps(HidP_Input, button_caps, &num_button_caps, buffer); + + for (USHORT i = 0; i < num_button_caps; ++i) { + HIDP_BUTTON_CAPS &cap = button_caps[i]; + int upper = 0; + if (cap.IsRange) { + upper = (cap.Range.UsageMax - cap.Range.UsageMin); + + if (device_cat.is_debug()) { + device_cat.debug() + << "Found button range: DataIndex=" << dec + << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax + << ", ReportID=" << (int)cap.ReportID + << ", UsagePage=0x" << hex << cap.UsagePage + << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax + << dec << "\n"; + } + } else { + if (device_cat.is_debug()) { + device_cat.debug() + << "Found button: DataIndex=" << dec << cap.NotRange.DataIndex + << ", ReportID=" << dec << (int)cap.ReportID + << ", UsagePage=0x" << cap.UsagePage + << ", Usage=0x" << cap.NotRange.Usage + << dec << "\n"; + } + } + + // Windows will only tell us which buttons in a report are "on", so we + // need to keep track of which buttons exist in which report so that we + // can figure out which ones are off. + if (cap.ReportID >= _report_buttons.size()) { + _report_buttons.resize(cap.ReportID + 1); + } + for (int j = 0; j <= upper; ++j) { + USAGE usage = j + cap.Range.UsageMin; + USHORT data_index = j + cap.Range.DataIndexMin; + ButtonHandle handle = ButtonHandle::none(); + switch (cap.UsagePage) { + case HID_USAGE_PAGE_BUTTON: + if (_device_class == DC_gamepad) { + static const ButtonHandle gamepad_buttons[] = { + ButtonHandle::none(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::start(), + GamepadButton::back(), + GamepadButton::lstick(), + GamepadButton::rstick(), + }; + if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + handle = gamepad_buttons[usage]; + } + } else if (_device_class == DC_flight_stick) { + if (usage > 0) { + handle = GamepadButton::joystick(usage - 1); + } + } else if (_device_class == DC_mouse) { + // In Panda, wheel and right button are flipped around... + int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); + handle = MouseButton::button(button); + } + break; + } + + int button_index = _buttons.size(); + _report_buttons[cap.ReportID].set_bit(button_index); + _indices[data_index] = Index::button(button_index); + _buttons.push_back(ButtonState(handle)); + } + } + + USHORT num_value_caps = caps.NumberInputValueCaps; + PHIDP_VALUE_CAPS value_caps = (PHIDP_VALUE_CAPS)alloca(num_value_caps * sizeof(HIDP_VALUE_CAPS)); + _HidP_GetValueCaps(HidP_Input, value_caps, &num_value_caps, buffer); + + _hat_data_index = -1; + + for (USHORT i = 0; i < num_value_caps; ++i) { + HIDP_VALUE_CAPS &cap = value_caps[i]; + int upper = 0; + if (cap.IsRange) { + upper = (cap.Range.UsageMax - cap.Range.UsageMin); + + if (device_cat.is_debug()) { + device_cat.debug() + << "Found value range: DataIndex=" << dec + << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax + << ", ReportID=" << (int)cap.ReportID + << ", UsagePage=0x" << hex << cap.UsagePage + << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax + << dec << ", LogicalMin=" << cap.LogicalMin + << ", LogicalMax=" << cap.LogicalMax << "\n"; + } + } else { + if (device_cat.is_debug()) { + device_cat.debug() + << "Found value: DataIndex=" << dec << cap.NotRange.DataIndex + << ", ReportID=" << dec << (int)cap.ReportID + << ", UsagePage=0x" << hex << cap.UsagePage + << ", Usage=0x" << cap.NotRange.Usage + << dec << ", LogicalMin=" << cap.LogicalMin + << ", LogicalMax=" << cap.LogicalMax << "\n"; + } + } + + for (int j = 0; j <= upper; ++j) { + USAGE usage = j + cap.Range.UsageMin; + USHORT data_index = j + cap.Range.DataIndexMin; + bool is_signed = true; + + // My gamepads give this odd invalid range. + if (cap.LogicalMin == 0 && cap.LogicalMax == -1) { + cap.LogicalMax = 65535; + is_signed = false; + } + + ControlAxis axis = C_none; + switch (cap.UsagePage) { + case HID_USAGE_PAGE_GENERIC: + switch (usage) { + case HID_USAGE_GENERIC_X: + if (_device_class == DC_gamepad) { + axis = C_left_x; + } else if (_device_class == DC_flight_stick) { + axis = C_roll; + } else { + axis = C_x; + } + break; + case HID_USAGE_GENERIC_Y: + if (_device_class == DC_gamepad) { + axis = C_left_y; + swap(cap.LogicalMin, cap.LogicalMax); + } else if (_device_class == DC_flight_stick) { + axis = C_pitch; + } else { + axis = C_y; + swap(cap.LogicalMin, cap.LogicalMax); + } + break; + case HID_USAGE_GENERIC_Z: + if (_device_class == DC_gamepad) { + axis = C_left_trigger; + } else if (_device_class == DC_flight_stick) { + axis = C_throttle; + } else { + axis = C_z; + swap(cap.LogicalMin, cap.LogicalMax); + } + break; + case HID_USAGE_GENERIC_RX: + if (_device_class == DC_gamepad) { + axis = C_right_x; + } else { + axis = C_pitch; + } + break; + case HID_USAGE_GENERIC_RY: + if (_device_class == DC_gamepad) { + axis = C_right_y; + } else { + axis = C_roll; + } + swap(cap.LogicalMin, cap.LogicalMax); + break; + case HID_USAGE_GENERIC_RZ: + if (_device_class == DC_gamepad) { + axis = C_right_trigger; + } else { + // Flip to match Panda's convention for heading. + axis = C_yaw; + swap(cap.LogicalMin, cap.LogicalMax); + } + break; + case HID_USAGE_GENERIC_SLIDER: + // Flip to match Panda's convention for heading. + axis = C_rudder; + swap(cap.LogicalMin, cap.LogicalMax); + break; + case HID_USAGE_GENERIC_WHEEL: + axis = C_wheel; + break; + case HID_USAGE_GENERIC_HATSWITCH: + // This is handled specially. + _hat_data_index = data_index; + _hat_data_minimum = cap.LogicalMin; + continue; + } + break; + } + + int control_index; + if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { + // T.Flight Hotas X throttle is reversed and can go backwards. + control_index = add_control(axis, cap.LogicalMax, cap.LogicalMin, true); + } else if (!is_signed) { + // All axes on the weird XInput-style mappings go from -1 to 1 + control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax, true); + } else { + control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax); + } + _indices[data_index] = Index::control(control_index, is_signed); + } + } + + // Do we need to emulate a hat switch or directional pad? + if (_hat_data_index != -1) { + _hat_left_button = (int)_buttons.size(); + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_left())); + _buttons.push_back(ButtonState(GamepadButton::hat_right())); + _buttons.push_back(ButtonState(GamepadButton::hat_down())); + _buttons.push_back(ButtonState(GamepadButton::hat_up())); + } + } + + _max_data_count = _HidP_MaxDataListLength(HidP_Input, buffer); + + _handle = handle; + _is_connected = true; + return true; +} + +/** + * Called by InputDeviceManager when this device is removed. + */ +void WinRawInputDevice:: +on_removal() { + LightMutexHolder holder(_lock); + _is_connected = false; + _handle = nullptr; + if (_preparsed != nullptr) { + delete _preparsed; + _preparsed = nullptr; + } + _indices.clear(); + _report_buttons.clear(); +} + +void WinRawInputDevice:: +on_input(PRAWINPUT input) { + nassertv(input != nullptr); + nassertv(_preparsed != nullptr); + + BYTE *ptr = input->data.hid.bRawData; + if (input->data.hid.dwSizeHid == 0) { + return; + } + + PHIDP_DATA data = (PHIDP_DATA)alloca(sizeof(HIDP_DATA) * _max_data_count); + nassertv(data != nullptr); + ULONG count; + + LightMutexHolder holder(_lock); + + for (DWORD i = 0; i < input->data.hid.dwCount; ++i) { + // The first byte is the report identifier. We need it to figure out + // which buttons are off, since each report only contains the buttons that + // are "on". + UCHAR report_id = ptr[0]; + BitArray unset_buttons = _report_buttons[report_id]; + + count = _max_data_count; + NTSTATUS status = _HidP_GetData(HidP_Input, data, &count, (PHIDP_PREPARSED_DATA)_preparsed, (PCHAR)ptr, input->data.hid.dwSizeHid); + if (status == HIDP_STATUS_SUCCESS) { + for (ULONG di = 0; di < count; ++di) { + if (data[di].DataIndex != _hat_data_index) { + const Index &idx = _indices[data[di].DataIndex]; + if (idx._control >= 0) { + if (idx._signed) { + control_changed(idx._control, (SHORT)data[di].RawValue); + } else { + control_changed(idx._control, data[di].RawValue); + } + } + if (idx._button >= 0) { + unset_buttons.clear_bit(idx._button); + button_changed(idx._button, (data[di].On != FALSE)); + } + } else { + int value = (int)data[di].RawValue - _hat_data_minimum; + button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left + button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right + button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down + button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up + } + } + + // Now unset the buttons in this report that aren't pressed. + int button_index = unset_buttons.get_lowest_on_bit(); + while (button_index >= 0) { + button_changed(button_index, false); + unset_buttons.clear_bit(button_index); + button_index = unset_buttons.get_lowest_on_bit(); + } + } else if (device_cat.is_spam()) { + device_cat.spam() + << "Failed to get data from raw device " << _path + << " (error 0x" << hex << (status & 0xffffffffu) << dec << ")\n"; + } + + ptr += input->data.hid.dwSizeHid; + } +} + +/** + * Polls the input device for new activity, to ensure it contains the latest + * events. This will only have any effect for some types of input devices; + * others may be updated automatically, and this method will be a no-op. + */ +void WinRawInputDevice:: +do_poll() { +} + +#endif // _WIN32 diff --git a/panda/src/device/winRawInputDevice.h b/panda/src/device/winRawInputDevice.h new file mode 100644 index 0000000000..58090ed07f --- /dev/null +++ b/panda/src/device/winRawInputDevice.h @@ -0,0 +1,85 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winRawInputDevice.h + * @author rdb + * @date 2018-01-19 + */ + +#ifndef WINRAWINPUTDEVICE_H +#define WINRAWINPUTDEVICE_H + +#include "pandabase.h" +#include "inputDevice.h" +#include "bitArray.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +class WinInputDeviceManager; + +/** + * This implementation of InputDevice uses the Win32 raw input API and the HID + * parser library to support a wide range of devices. + */ +class EXPCL_PANDA_DEVICE WinRawInputDevice FINAL : public InputDevice { +public: + WinRawInputDevice(WinInputDeviceManager *manager, const char *path); + ~WinRawInputDevice(); + + bool on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name); + void on_removal(); + void on_input(PRAWINPUT input); + +private: + virtual void do_poll(); + +private: + const string _path; + HANDLE _handle; + DWORD _size; + void *_preparsed; + ULONG _max_data_count; + ULONG _max_usage_count; + + // Indexed by report ID + pvector _report_buttons; + + // Either a button index or a control index. + struct Index { + Index() : _button(-1), _control(-1) {} + + static Index button(int index) { + Index idx; + idx._button = index; + return idx; + } + static Index control(int index, bool is_signed=true) { + Index idx; + idx._control = index; + idx._signed = is_signed; + return idx; + } + + int _button; + int _control; + bool _signed; + }; + + // Maps a "data index" to either button index or control index. + pvector _indices; + int _hat_data_index; + int _hat_data_minimum; + int _hat_left_button; + + WinInputDeviceManager *_manager; + friend class WinInputDeviceManager; +}; + +#endif // _WIN32 + +#endif diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 57fa73a416..5c8e9e8b2f 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -13,7 +13,8 @@ #include "xInputDevice.h" -#ifdef _WIN32 +#if defined(_WIN32) && !defined(CPPPARSER) + #include "gamepadButton.h" #include "inputDeviceManager.h" #include "string_utils.h" @@ -73,27 +74,44 @@ typedef struct _XINPUT_BATTERY_INFORMATION { } XINPUT_BATTERY_INFORMATION; // Undocumented, I figured out how this looks by trial and error. -struct XINPUT_BUSINFO { +typedef struct _XINPUT_BUSINFO { WORD VendorID; WORD ProductID; WORD RevisionID; WORD Unknown1; // Unknown - padding? DWORD InstanceID; DWORD Unknown2; - //WORD Unknown3; -}; + WORD Unknown3; +} XINPUT_BUSINFO; + +typedef struct _XINPUT_CAPABILITIES_EX { + BYTE Type; + BYTE SubType; + WORD Flags; + XINPUT_GAMEPAD Gamepad; + XINPUT_VIBRATION Vibration; + + // The following fields are undocumented. + WORD VendorID; + WORD ProductID; + WORD RevisionID; + WORD Unknown1; + WORD Unknown2; +} XINPUT_CAPABILITIES_EX; typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); typedef DWORD (*pXInputSetState)(DWORD, XINPUT_VIBRATION *); typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); +typedef DWORD (*pXInputGetCapabilitiesEx)(DWORD, DWORD, DWORD, XINPUT_CAPABILITIES_EX *); typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *); -static pXInputGetState get_state = NULL; -static pXInputSetState set_state = NULL; -static pXInputGetCapabilities get_capabilities = NULL; -static pXInputGetBatteryInformation get_battery_information = NULL; -static pXInputGetBaseBusInformation get_base_bus_information = NULL; +static pXInputGetState get_state = nullptr; +static pXInputSetState set_state = nullptr; +static pXInputGetCapabilities get_capabilities = nullptr; +static pXInputGetCapabilitiesEx get_capabilities_ex = nullptr; +static pXInputGetBatteryInformation get_battery_information = nullptr; +static pXInputGetBaseBusInformation get_base_bus_information = nullptr; bool XInputDevice::_initialized = false; @@ -108,26 +126,8 @@ XInputDevice(DWORD user_index) : nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT); - if (!_initialized) { - nassertv(init_xinput()); - } - - _name = "XInput Device #"; - _name += format_string(user_index); - _controls.resize(6); _buttons.resize(16); - - // Check if the device is connected. If so, initialize it. - XINPUT_CAPABILITIES caps; - XINPUT_STATE state; - if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS && - get_state(_index, &state) == ERROR_SUCCESS) { - _is_connected = true; - init_device(caps, state); - } else { - _is_connected = false; - } } /** @@ -138,17 +138,79 @@ XInputDevice:: do_set_vibration(0, 0); } +/** + * Called when a new input device arrives in the InputDeviceManager. This + * method checks whether it matches this XInput device. + */ +bool XInputDevice:: +check_arrival(const RID_DEVICE_INFO &info, DEVINST inst, + const string &name, const string &manufacturer) { + LightMutexHolder holder(_lock); + if (_is_connected) { + return false; + } + + if (!_initialized) { + nassertr_always(init_xinput(), false); + } + + XINPUT_CAPABILITIES_EX caps = {0}; + XINPUT_STATE state; + if ((get_capabilities_ex && get_capabilities_ex(1, _index, 0, &caps) != ERROR_SUCCESS) && + get_capabilities(_index, 0, (XINPUT_CAPABILITIES *)&caps) != ERROR_SUCCESS) { + return false; + } + + // Extra check for VID/PID if we have it, just to be sure. + if ((caps.VendorID != 0 && caps.VendorID != info.hid.dwVendorId) || + (caps.ProductID != 0 && caps.ProductID != info.hid.dwProductId)) { + return false; + } + + // Yes, take the name and manufacturer. + if (!name.empty()) { + _name = name; + } else { + _name = "XInput Device #"; + _name += format_string(_index + 1); + } + _manufacturer = manufacturer; + + if (inst && caps.ProductID == 0 && caps.RevisionID != 0) { + // XInput does not report a product ID for the Xbox 360 wireless adapter. + // Instead, we check that the RevisionID matches. + char buffer[4096]; + ULONG buflen = sizeof(buffer); + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_HARDWAREID, 0, buffer, &buflen, 0) == CR_SUCCESS) { + string ids(buffer, buflen); + char revstr[16]; + sprintf(revstr, "REV_%04x", caps.RevisionID); + if (ids.find(revstr) == string::npos) { + return false; + } + } + } + + _is_connected = true; + init_device(caps, state); + _vendor_id = info.hid.dwVendorId; + _product_id = info.hid.dwProductId; + return true; +} + /** * Called periodically by the InputDeviceManager to detect whether the device * is currently connected. + * Returns true if the device wasn't connected, but now is. */ void XInputDevice:: detect(InputDeviceManager *mgr) { bool connected = false; - XINPUT_CAPABILITIES caps; + XINPUT_CAPABILITIES_EX caps = {0}; XINPUT_STATE state; - if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS && + if (((get_capabilities_ex && get_capabilities_ex(1, _index, 0, &caps) == ERROR_SUCCESS) || + get_capabilities(_index, 0, (XINPUT_CAPABILITIES *)&caps) == ERROR_SUCCESS) && get_state(_index, &state) == ERROR_SUCCESS) { connected = true; } else { @@ -203,9 +265,9 @@ init_xinput() { // Undocumented version (XInputGetStateEx) that includes a // state bit for the guide button. get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100)); - if (get_state == NULL) { + if (get_state == nullptr) { get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState"); - if (get_state == NULL) { + if (get_state == nullptr) { device_cat.error() << "Failed to find function XInputGetState in " << dll_name << ".\n"; return false; @@ -213,14 +275,14 @@ init_xinput() { } set_state = (pXInputSetState)GetProcAddress(module, "XInputSetState"); - if (set_state == NULL) { + if (set_state == nullptr) { device_cat.error() << "Failed to find function XInputSetState in " << dll_name << ".\n"; return false; } get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); - if (get_capabilities == NULL) { + if (get_capabilities == nullptr) { device_cat.error() << "Failed to find function XInputGetCapabilities in " << dll_name << ".\n"; return false; @@ -228,6 +290,7 @@ init_xinput() { get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation"); get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104)); + get_capabilities_ex = (pXInputGetCapabilitiesEx)GetProcAddress(module, MAKEINTRESOURCE(108)); return true; } @@ -240,7 +303,8 @@ init_xinput() { * Initializes the device. Called when the device was just connected. */ void XInputDevice:: -init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { +init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { + nassertv(_initialized); // It seems that the Xbox One controller is reported as having a DevType of // zero, at least when I tested in with XInput 1.3 on Windows 7. //if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { @@ -275,8 +339,8 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { set_control_map(1, C_throttle); set_control_map(2, C_roll); set_control_map(3, C_pitch); - set_control_map(4, C_hat_x); - set_control_map(5, C_hat_y); + set_control_map(4, C_none); + set_control_map(5, C_none); break; case XINPUT_DEVSUBTYPE_DANCE_PAD: @@ -327,11 +391,13 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { set_button_map(13, GamepadButton::action_x()); set_button_map(14, GamepadButton::action_y()); - if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { + + if (caps.Vibration.wLeftMotorSpeed != 0 || + caps.Vibration.wRightMotorSpeed != 0) { _flags |= IDF_has_vibration; } - if (get_battery_information != NULL) { + if (get_battery_information != nullptr) { XINPUT_BATTERY_INFORMATION batt; if (get_battery_information(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) { if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED && @@ -344,54 +410,11 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { } } - // Get information about the USB device. - // This is not documented at all. I'm probably the first to try this. - XINPUT_BUSINFO businfo; - if (get_base_bus_information != NULL && - get_base_bus_information(0, &businfo) == ERROR_SUCCESS) { - _vendor_id = businfo.VendorID; - _product_id = businfo.ProductID; - - { - // Reformat the serial number into its original hex string form. - char sn[10]; - sprintf_s(sn, 10, "%08X", businfo.InstanceID); - _serial_number.assign(sn, 8); - } - - // Get information about the device from Windows. For that, we'll - // first need to construct the device path. Fortunately, we now have - // enough information to do so. - char path[32]; - sprintf_s(path, 32, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID); - - DEVINST inst; - if (CM_Locate_DevNodeA(&inst, path, 0) != 0) { - if (device_cat.is_debug()) { - device_cat.debug() - << "Could not locate device node " << path << "\n"; - } - } else { - // Get the device properties we need. - char buffer[4096]; - ULONG buflen = 4096; - if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) { - _name.assign(buffer); - } - buflen = 4096; - if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) { - _manufacturer.assign(buffer); - } - } - } - WORD buttons = state.Gamepad.wButtons; WORD mask = 1; for (int i = 0; i < 16; ++i) { - if (buttons & mask) { - // Set the state without triggering a button event. - _buttons[i].state = (buttons & mask) ? S_down : S_up; - } + // Set the state without triggering a button event. + _buttons[i].state = (buttons & mask) ? S_down : S_up; mask <<= 1; if (i == 10) { // XInput skips 0x0800. @@ -417,6 +440,8 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { */ void XInputDevice:: do_set_vibration(double strong, double weak) { + nassertv_always(_is_connected); + XINPUT_VIBRATION vibration; vibration.wLeftMotorSpeed = strong * 0xffff; vibration.wRightMotorSpeed = weak * 0xffff; @@ -430,6 +455,11 @@ do_set_vibration(double strong, double weak) { */ void XInputDevice:: do_poll() { + // Not sure why someone would call this on a disconnected device. + if (!_is_connected) { + return; + } + XINPUT_STATE state; if (get_state(_index, &state) != ERROR_SUCCESS) { @@ -440,18 +470,6 @@ do_poll() { mgr->remove_device(this); } return; - - } else if (!_is_connected) { - // Device was (re)connected. It's a bit strange to call poll() on a - // disconnected device, but there's nothing stopping the user from - // doing so. - XINPUT_CAPABILITIES caps; - if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS) { - _is_connected = true; - init_device(caps, state); - InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); - mgr->add_device(this); - } } if (state.dwPacketNumber == _last_packet) { diff --git a/panda/src/device/xInputDevice.h b/panda/src/device/xInputDevice.h index 73d8c8747d..598cd5ec74 100644 --- a/panda/src/device/xInputDevice.h +++ b/panda/src/device/xInputDevice.h @@ -17,13 +17,17 @@ #include "pandabase.h" #include "inputDevice.h" -#ifdef _WIN32 +#if defined(_WIN32) && !defined(CPPPARSER) + +#include class InputDeviceManager; -typedef struct _XINPUT_CAPABILITIES XINPUT_CAPABILITIES; +typedef struct _XINPUT_CAPABILITIES_EX XINPUT_CAPABILITIES_EX; typedef struct _XINPUT_STATE XINPUT_STATE; +typedef struct tagRID_DEVICE_INFO RID_DEVICE_INFO; + /** * This implementation of InputDevice uses Microsoft's XInput library to * interface with an Xbox 360 game controller. @@ -33,11 +37,13 @@ public: XInputDevice(DWORD user_index); ~XInputDevice(); + bool check_arrival(const RID_DEVICE_INFO &info, DEVINST inst, + const string &name, const string &manufacturer); void detect(InputDeviceManager *mgr); static bool init_xinput(); private: - void init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state); + void init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state); virtual void do_set_vibration(double strong, double weak); virtual void do_poll(); From 712b246dd6839f5423db0c20c94ea26df5cae19b Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 23 Jan 2018 12:56:42 +0100 Subject: [PATCH 49/82] input: reverse rudder direction to match yaw, reverse 3D mouse roll Although having the rudder reversed is less intuitive, this makes it match the Panda convention for "heading". --- panda/src/device/evdevInputDevice.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index cf20e57261..115ce8ee77 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -518,9 +518,9 @@ init_device() { // We'd like to reverse the Y axis to match the XInput behavior. // Also reverse the yaw axis to match right-hand coordinate system. // Also T.Flight Hotas X throttle is reversed and can go backwards. - if (axis == C_yaw || axis == C_left_y || axis == C_right_y || + if (axis == C_yaw || axis == C_rudder || axis == C_left_y || axis == C_right_y || (axis == C_throttle && (quirks & QB_reversed_throttle) != 0) || - (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z))) { + (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z || axis == C_roll))) { swap(absinfo.maximum, absinfo.minimum); } if (axis == C_throttle && (quirks & QB_centered_throttle) != 0) { From f189b817c6d5377e1bf81f7fc55d560b3d3057f2 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 23 Jan 2018 12:59:14 +0100 Subject: [PATCH 50/82] input: fix name/manufacturer/serial fetching for some evdev devices --- panda/src/device/evdevInputDevice.cxx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 115ce8ee77..6855685084 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -572,8 +572,14 @@ init_device() { char path[64]; char buffer[256]; + const char *parent = ""; sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); FILE *f = fopen(path, "r"); + if (!f) { + parent = "../"; + sprintf(path, "/sys/class/input/event%d/device/device/%s../product", _index, parent); + f = fopen(path, "r"); + } if (f) { if (fgets(buffer, sizeof(buffer), f) != NULL) { buffer[strcspn(buffer, "\r\n")] = 0; @@ -583,7 +589,7 @@ init_device() { } fclose(f); } - sprintf(path, "/sys/class/input/event%d/device/device/../manufacturer", _index); + sprintf(path, "/sys/class/input/event%d/device/device/%s../manufacturer", _index, parent); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != NULL) { @@ -592,7 +598,7 @@ init_device() { } fclose(f); } - sprintf(path, "/sys/class/input/event%d/device/device/../serial", _index); + sprintf(path, "/sys/class/input/event%d/device/device/%s../serial", _index, parent); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != NULL) { From 3da32b7b7cd00ba5dfe9abbe251848665d105c61 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 25 Jan 2018 20:14:35 +0100 Subject: [PATCH 51/82] input: split out LinuxInputDeviceManager from InputDeviceManager --- panda/src/device/evdevInputDevice.cxx | 10 +- panda/src/device/evdevInputDevice.h | 9 +- panda/src/device/inputDeviceManager.cxx | 283 +----------------- panda/src/device/inputDeviceManager.h | 3 - panda/src/device/linuxInputDeviceManager.cxx | 294 +++++++++++++++++++ panda/src/device/linuxInputDeviceManager.h | 45 +++ panda/src/device/linuxJoystickDevice.cxx | 9 +- panda/src/device/linuxJoystickDevice.h | 8 +- panda/src/device/p3device_composite1.cxx | 1 + panda/src/x11display/x11GraphicsWindow.cxx | 2 +- 10 files changed, 371 insertions(+), 293 deletions(-) create mode 100644 panda/src/device/linuxInputDeviceManager.cxx create mode 100644 panda/src/device/linuxInputDeviceManager.h diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 6855685084..a4bdc43806 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -18,7 +18,7 @@ #include "gamepadButton.h" #include "keyboardButton.h" #include "mouseButton.h" -#include "inputDeviceManager.h" +#include "linuxInputDeviceManager.h" #include #include @@ -79,7 +79,8 @@ TypeHandle EvdevInputDevice::_type_handle; * Creates a new device representing the evdev device with the given index. */ EvdevInputDevice:: -EvdevInputDevice(int index) : +EvdevInputDevice(LinuxInputDeviceManager *manager, int index) : + _manager(manager), _index(index), _fd(-1), _can_write(false), @@ -203,8 +204,9 @@ do_poll() { // If we got events, we are obviously connected. Mark us so. if (!_is_connected) { _is_connected = true; - InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); - mgr->add_device(this); + if (_manager != nullptr) { + _manager->add_device(this); + } } } } diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 0a6baa058a..8f73d04a20 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -18,6 +18,8 @@ #ifdef PHAVE_LINUX_INPUT_H +class LinuxInputDeviceManager; + /** * This is a type of device that uses the Linux /dev/input/event# API to read * data from a raw mouse or other input device. Unlike the joystick API, the @@ -25,7 +27,7 @@ */ class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { public: - EvdevInputDevice(int index); + EvdevInputDevice(LinuxInputDeviceManager *manager, int index); virtual ~EvdevInputDevice(); private: @@ -36,8 +38,11 @@ private: bool process_events(); private: - int _index; + LinuxInputDeviceManager *_manager; + int _fd; + int _index; + bool _can_write; int _ff_id; bool _ff_playing; diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 31f7f93934..a4ee1c4c8e 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -13,17 +13,10 @@ #include "inputDeviceManager.h" #include "ioKitInputDevice.h" -#include "linuxJoystickDevice.h" +#include "linuxInputDeviceManager.h" #include "winInputDeviceManager.h" #include "throw_event.h" -#ifdef PHAVE_LINUX_INPUT_H -#include -#include -#include -#include -#endif - InputDeviceManager *InputDeviceManager::_global_ptr = NULL; /** @@ -31,51 +24,7 @@ InputDeviceManager *InputDeviceManager::_global_ptr = NULL; * connected and setting up any platform-dependent structures necessary for * listening for future device connect events. */ -#ifdef PHAVE_LINUX_INPUT_H -InputDeviceManager:: -InputDeviceManager() : _inotify_fd(-1) { - // Use inotify to watch /dev/input for hotplugging of devices. - _inotify_fd = inotify_init1(O_NONBLOCK); - - if (_inotify_fd < 0) { - device_cat.error() - << "Error initializing inotify: " << strerror(errno) << "\n"; - - } else if (inotify_add_watch(_inotify_fd, "/dev/input", IN_CREATE | IN_ATTRIB | IN_DELETE) < 0) { - device_cat.error() - << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; - } - - // Scan /dev/input for a list of input devices. - DIR *dir = opendir("/dev/input"); - if (dir) { - vector_int indices; - dirent *entry = readdir(dir); - while (entry != NULL) { - int index = -1; - if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) { - indices.push_back(index); - } - entry = readdir(dir); - } - closedir(dir); - - // We'll want to sort the devices by index, since the order may be - // meaningful (eg. for the Xbox wireless receiver). - std::sort(indices.begin(), indices.end()); - _evdev_devices.resize(indices.back() + 1, NULL); - - vector_int::const_iterator it; - for (it = indices.begin(); it != indices.end(); ++it) { - consider_add_evdev_device(*it); - } - } else { - device_cat.error() - << "Error opening directory /dev/input: " << strerror(errno) << "\n"; - return; - } -} -#elif defined(__APPLE__) +#if defined(__APPLE__) InputDeviceManager:: InputDeviceManager() { _hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); @@ -128,12 +77,7 @@ InputDeviceManager() : _lock("InputDeviceManager") { */ InputDeviceManager:: ~InputDeviceManager() { -#ifdef PHAVE_LINUX_INPUT_H - if (_inotify_fd >= 0) { - close(_inotify_fd); - _inotify_fd = -1; - } -#elif defined(__APPLE__) +#if defined(__APPLE__) IOHIDManagerUnscheduleFromRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); IOHIDManagerClose(_hid_manager, kIOHIDOptionsTypeNone); CFRelease(_hid_manager); @@ -147,129 +91,13 @@ void InputDeviceManager:: make_global_ptr() { #ifdef _WIN32 _global_ptr = new WinInputDeviceManager; +#elif defined(PHAVE_LINUX_INPUT_H) + _global_ptr = new LinuxInputDeviceManager; #else _global_ptr = new InputDeviceManager; #endif } -#ifdef PHAVE_LINUX_INPUT_H -/** - * Checks whether the event device with the given index is accessible, and if - * so, adds it. Returns the device if it was newly connected. - * - * This is the preferred interface for input devices on Linux. - */ -InputDevice *InputDeviceManager:: -consider_add_evdev_device(int ev_index) { - if (ev_index < _evdev_devices.size()) { - if (_evdev_devices[ev_index] != NULL) { - // We already have this device. FIXME: probe it and add it to the - // list of connected devices? - return NULL; - } - } else { - // Make room to store this index. - _evdev_devices.resize(ev_index + 1, NULL); - } - - // Check if we can directly read the event device. - char path[64]; - sprintf(path, "/dev/input/event%d", ev_index); - - if (access(path, R_OK) == 0) { - PT(InputDevice) device = new EvdevInputDevice(ev_index); - if (device_cat.is_debug()) { - device_cat.debug() - << "Discovered evdev input device " << *device << "\n"; - } - - _evdev_devices[ev_index] = device; - - if (device->is_connected()) { - _connected_devices.add_device(MOVE(device)); - } else { - // Wait for activity on the device before it is considered connected. - _inactive_devices.add_device(MOVE(device)); - } - return _evdev_devices[ev_index]; - } - - // Nope. The permissions haven't been configured to allow it. Check if this - // corresponds to a /dev/input/jsX interface, which has a better chance of - // having read permissions set, but doesn't export all of the features - // (notably, force feedback). - - // We do this by checking for a js# directory inside the sysfs directory. - sprintf(path, "/sys/class/input/event%d/device", ev_index); - - DIR *dir = opendir(path); - if (dir == NULL) { - if (device_cat.is_debug()) { - device_cat.debug() - << "Error opening directory " << path << ": " << strerror(errno) << "\n"; - } - return NULL; - } - - dirent *entry = readdir(dir); - while (entry != NULL) { - int js_index = -1; - if (sscanf(entry->d_name, "js%d", &js_index) == 1) { - // Yes, we fonud a corresponding js device. Try adding it. - closedir(dir); - - InputDevice *device = consider_add_js_device(js_index); - if (device != NULL && device_cat.is_warning()) { - // This seemed to work. Display a warning to the user indicating - // that they might want to configure udev properly. - device_cat.warning() - << "/dev/input/event" << ev_index << " is not readable, some " - "features will be unavailable.\n"; - } - _evdev_devices[ev_index] = device; - return device; - } - entry = readdir(dir); - } - - closedir(dir); - return NULL; -} - -/** - * Checks whether the joystick device with the given index is accessible, and - * if so, adds it. Returns the device if it was newly connected. - * - * This is only used on Linux as a fallback interface for when an evdev device - * cannot be accessed. - */ -InputDevice *InputDeviceManager:: -consider_add_js_device(int js_index) { - char path[64]; - sprintf(path, "/dev/input/js%d", js_index); - - if (access(path, R_OK) == 0) { - PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(js_index); - if (device_cat.is_debug()) { - device_cat.debug() - << "Discovered joydev input device " << *device << "\n"; - } - InputDevice *device_p = device.p(); - - if (device->is_connected()) { - _connected_devices.add_device(MOVE(device)); - } else { - // Wait for activity on the device before it is considered connected. - _inactive_devices.add_device(MOVE(device)); - } - return device_p; - } - - return NULL; -} - -#endif - /** * Description: Returns all currently connected devices. */ @@ -348,105 +176,4 @@ remove_device(InputDevice *device) { */ void InputDeviceManager:: update() { -#ifdef PHAVE_LINUX_INPUT_H - // Check for any devices that may be disconnected and need to be probed in - // order to see whether they have been reconnected. - InputDeviceSet inactive_devices; - { - LightMutexHolder holder(_lock); - inactive_devices = _inactive_devices; - } - for (size_t i = 0; i < inactive_devices.size(); ++i) { - InputDevice *device = inactive_devices[i]; - if (device != NULL && !device->is_connected()) { - device->poll(); - } - } - - // We use inotify to tell us whether a device was added, removed, or has - // changed permissions to allow us to access it. - unsigned int avail = 0; - ioctl(_inotify_fd, FIONREAD, &avail); - if (avail == 0) { - return; - } - - char buffer[avail]; - int n_read = read(_inotify_fd, buffer, avail); - if (n_read < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // No data available for now. - - } else { - device_cat.error() << "read: " << strerror(errno) << "\n"; - } - return; - } - - LightMutexHolder holder(_lock); - - // Iterate over the events in the buffer. - char *ptr = buffer; - char *end = buffer + avail; - while (ptr < end) { - inotify_event *event = (inotify_event *)ptr; - - string name(event->name); - - if (event->mask & IN_DELETE) { - // The device was deleted. If we have it, remove it. - - int index = -1; - if (sscanf(event->name, "event%d", &index) == 1) { - // Check if we have this evdev device. If so, disconnect it. - if (index < _evdev_devices.size()) { - PT(InputDevice) device = _evdev_devices[index]; - if (device != NULL) { - _evdev_devices[index] = NULL; - _inactive_devices.remove_device(device); - if (_connected_devices.remove_device(device)) { - throw_event("disconnect-device", device.p()); - } - - if (device_cat.is_debug()) { - device_cat.debug() - << "Removed input device " << *device << "\n"; - } - } - } - } - - } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { - // The device was created, or it was chmodded to be accessible. We need - // to check for the latter since it seems that the device may get the - // IN_CREATE event before the driver gets the permissions set properly. - - int index = -1; - if (sscanf(event->name, "event%d", &index) == 1) { - InputDevice *device = consider_add_evdev_device(index); - if (device != NULL && device->is_connected()) { - throw_event("connect-device", device); - } - } - } - - ptr += sizeof(inotify_event) + event->len; - } -#endif } - -#if defined(__APPLE__) && !defined(CPPPARSER) -void InputDeviceManager:: -on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) { - InputDeviceManager *mgr = (InputDeviceManager *)ctx; - nassertv(mgr != nullptr); - nassertv(device); - - PT(InputDevice) input_device = new IOKitInputDevice(device); - if (device_cat.is_debug()) { - device_cat.debug() - << "Discovered input device " << *input_device << "\n"; - } - mgr->add_device(input_device); -} -#endif diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index c6c50a33d9..5f827cd3a9 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -62,9 +62,6 @@ protected: LightMutex _lock; #ifdef PHAVE_LINUX_INPUT_H - int _inotify_fd; - - pvector _evdev_devices; InputDeviceSet _inactive_devices; #endif diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx new file mode 100644 index 0000000000..61eed54b42 --- /dev/null +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -0,0 +1,294 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file linuxInputDeviceManager.cxx + * @author rdb + * @date 2018-01-25 + */ + +#include "inputDeviceManager.h" +#include "throw_event.h" + +#ifdef PHAVE_LINUX_INPUT_H + +#include "evdevInputDevice.h" +#include "linuxJoystickDevice.h" + +#include +#include +#include +#include + +/** + * Initializes the device manager by scanning which devices are currently + * connected and setting up any platform-dependent structures necessary for + * listening for future device connect events. + */ +LinuxInputDeviceManager:: +LinuxInputDeviceManager() { + // Use inotify to watch /dev/input for hotplugging of devices. + _inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + + if (_inotify_fd < 0) { + device_cat.error() + << "Error initializing inotify: " << strerror(errno) << "\n"; + + } else if (inotify_add_watch(_inotify_fd, "/dev/input", IN_CREATE | IN_ATTRIB | IN_DELETE) < 0) { + device_cat.error() + << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; + } + + // Scan /dev/input for a list of input devices. + DIR *dir = opendir("/dev/input"); + if (dir) { + vector_int indices; + dirent *entry = readdir(dir); + while (entry != nullptr) { + int index = -1; + if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) { + indices.push_back(index); + } + entry = readdir(dir); + } + closedir(dir); + + // We'll want to sort the devices by index, since the order may be + // meaningful (eg. for the Xbox wireless receiver). + std::sort(indices.begin(), indices.end()); + _evdev_devices.resize(indices.back() + 1, nullptr); + + vector_int::const_iterator it; + for (it = indices.begin(); it != indices.end(); ++it) { + consider_add_evdev_device(*it); + } + } else { + device_cat.error() + << "Error opening directory /dev/input: " << strerror(errno) << "\n"; + return; + } +} + +/** + * Closes any resources that the device manager was using to listen for events. + */ +LinuxInputDeviceManager:: +~LinuxInputDeviceManager() { + if (_inotify_fd >= 0) { + close(_inotify_fd); + _inotify_fd = -1; + } +} + +/** + * Checks whether the event device with the given index is accessible, and if + * so, adds it. Returns the device if it was newly connected. + * + * This is the preferred interface for input devices on Linux. + */ +InputDevice *LinuxInputDeviceManager:: +consider_add_evdev_device(int ev_index) { + if (ev_index < _evdev_devices.size()) { + if (_evdev_devices[ev_index] != nullptr) { + // We already have this device. FIXME: probe it and add it to the + // list of connected devices? + return nullptr; + } + } else { + // Make room to store this index. + _evdev_devices.resize(ev_index + 1, nullptr); + } + + // Check if we can directly read the event device. + char path[64]; + sprintf(path, "/dev/input/event%d", ev_index); + + if (access(path, R_OK) == 0) { + PT(InputDevice) device = new EvdevInputDevice(this, ev_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered evdev input device " << *device << "\n"; + } + + _evdev_devices[ev_index] = device; + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return _evdev_devices[ev_index]; + } + + // Nope. The permissions haven't been configured to allow it. Check if this + // corresponds to a /dev/input/jsX interface, which has a better chance of + // having read permissions set, but doesn't export all of the features + // (notably, force feedback). + + // We do this by checking for a js# directory inside the sysfs directory. + sprintf(path, "/sys/class/input/event%d/device", ev_index); + + DIR *dir = opendir(path); + if (dir == nullptr) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Error opening directory " << path << ": " << strerror(errno) << "\n"; + } + return nullptr; + } + + dirent *entry = readdir(dir); + while (entry != nullptr) { + int js_index = -1; + if (sscanf(entry->d_name, "js%d", &js_index) == 1) { + // Yes, we fonud a corresponding js device. Try adding it. + closedir(dir); + + InputDevice *device = consider_add_js_device(js_index); + if (device != nullptr && device_cat.is_warning()) { + // This seemed to work. Display a warning to the user indicating + // that they might want to configure udev properly. + device_cat.warning() + << "/dev/input/event" << ev_index << " is not readable, some " + "features will be unavailable.\n"; + } + _evdev_devices[ev_index] = device; + return device; + } + entry = readdir(dir); + } + + closedir(dir); + return nullptr; +} + +/** + * Checks whether the joystick device with the given index is accessible, and + * if so, adds it. Returns the device if it was newly connected. + * + * This is only used on Linux as a fallback interface for when an evdev device + * cannot be accessed. + */ +InputDevice *LinuxInputDeviceManager:: +consider_add_js_device(int js_index) { + char path[64]; + sprintf(path, "/dev/input/js%d", js_index); + + if (access(path, R_OK) == 0) { + PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(this, js_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered joydev input device " << *device << "\n"; + } + InputDevice *device_p = device.p(); + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return device_p; + } + + return nullptr; +} + +/** + * Polls the system to see if there are any new devices. In some + * implementations this is a no-op. + */ +void LinuxInputDeviceManager:: +update() { + // Check for any devices that may be disconnected and need to be probed in + // order to see whether they have been reconnected. + InputDeviceSet inactive_devices; + { + LightMutexHolder holder(_lock); + inactive_devices = _inactive_devices; + } + for (size_t i = 0; i < inactive_devices.size(); ++i) { + InputDevice *device = inactive_devices[i]; + if (device != nullptr && !device->is_connected()) { + device->poll(); + } + } + + // We use inotify to tell us whether a device was added, removed, or has + // changed permissions to allow us to access it. + unsigned int avail = 0; + ioctl(_inotify_fd, FIONREAD, &avail); + if (avail == 0) { + return; + } + + char buffer[avail]; + int n_read = read(_inotify_fd, buffer, avail); + if (n_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available for now. + + } else { + device_cat.error() << "read: " << strerror(errno) << "\n"; + } + return; + } + + LightMutexHolder holder(_lock); + + // Iterate over the events in the buffer. + char *ptr = buffer; + char *end = buffer + avail; + while (ptr < end) { + inotify_event *event = (inotify_event *)ptr; + + string name(event->name); + + if (event->mask & IN_DELETE) { + // The device was deleted. If we have it, remove it. + + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + // Check if we have this evdev device. If so, disconnect it. + if (index < _evdev_devices.size()) { + PT(InputDevice) device = _evdev_devices[index]; + if (device != nullptr) { + device->set_connected(false); + _evdev_devices[index] = nullptr; + _inactive_devices.remove_device(device); + if (_connected_devices.remove_device(device)) { + throw_event("disconnect-device", device.p()); + } + + if (device_cat.is_debug()) { + device_cat.debug() + << "Removed input device " << *device << "\n"; + } + } + } + } + + } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { + // The device was created, or it was chmodded to be accessible. We need + // to check for the latter since it seems that the device may get the + // IN_CREATE event before the driver gets the permissions set properly. + + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + InputDevice *device = consider_add_evdev_device(index); + if (device != nullptr && device->is_connected()) { + throw_event("connect-device", device); + } + } + } + + ptr += sizeof(inotify_event) + event->len; + } +} + +#endif // PHAVE_LINUX_INPUT_H diff --git a/panda/src/device/linuxInputDeviceManager.h b/panda/src/device/linuxInputDeviceManager.h new file mode 100644 index 0000000000..fd82efc689 --- /dev/null +++ b/panda/src/device/linuxInputDeviceManager.h @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file linuxInputDeviceManager.h + * @author rdb + * @date 2018-01-25 + */ + +#ifndef LINUXINPUTDEVICEMANAGER_H +#define LINUXINPUTDEVICEMANAGER_H + +#include "inputDeviceManager.h" + +#ifdef PHAVE_LINUX_INPUT_H + +/** + * This class keeps track of all the devices on a system, and sends out events + * when a device has been hot-plugged. + */ +class EXPCL_PANDA_DEVICE LinuxInputDeviceManager FINAL : public InputDeviceManager { +private: + LinuxInputDeviceManager(); + ~LinuxInputDeviceManager(); + + InputDevice *consider_add_evdev_device(int index); + InputDevice *consider_add_js_device(int index); + + virtual void update(); + +protected: + int _inotify_fd; + + pvector _evdev_devices; + + friend class InputDeviceManager; +}; + +#endif // PHAVE_LINUX_INPUT_H + +#endif diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index f510e02f6a..d3fea162bd 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -17,6 +17,7 @@ #ifdef PHAVE_LINUX_INPUT_H #include "gamepadButton.h" +#include "linuxInputDeviceManager.h" #include #include @@ -27,7 +28,8 @@ TypeHandle LinuxJoystickDevice::_type_handle; * Creates a new device using the Linux joystick device with the given index. */ LinuxJoystickDevice:: -LinuxJoystickDevice(int index) : +LinuxJoystickDevice(LinuxInputDeviceManager *manager, int index) : + _manager(manager), _fd(-1), _index(index), _dpad_x_axis(-1), @@ -79,8 +81,9 @@ do_poll() { // If we got events, we are obviously connected. Mark us so. if (!_is_connected) { _is_connected = true; - InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); - mgr->add_device(this); + if (_manager != nullptr) { + _manager->add_device(this); + } } } } diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index ab97dd5293..fad69730c1 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -18,13 +18,15 @@ #ifdef PHAVE_LINUX_INPUT_H +class LinuxInputDeviceManager; + /** * This is a type of device that uses the Linux /dev/input/js# API to read * data from a game controller. */ class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice { -public: - LinuxJoystickDevice(int index); +PUBLISHED: + LinuxJoystickDevice(LinuxInputDeviceManager *manager, int index); virtual ~LinuxJoystickDevice(); bool check_events() const; @@ -36,6 +38,8 @@ private: bool process_events(); private: + LinuxInputDeviceManager *_manager; + int _fd; int _index; diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 65a03ccc48..0067579474 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -11,6 +11,7 @@ #include "inputDeviceNode.cxx" #include "inputDeviceSet.cxx" #include "ioKitInputDevice.cxx" +#include "linuxInputDeviceManager.cxx" #include "linuxJoystickDevice.cxx" #include "winInputDeviceManager.cxx" #include "winRawInputDevice.cxx" diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index a0bd1013a4..8e4776e292 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -1245,7 +1245,7 @@ open_raw_mice() { string fn = fnb.str(); int fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK, 0); if (fd >= 0) { - EvdevInputDevice *device = new EvdevInputDevice(fd); + EvdevInputDevice *device = new EvdevInputDevice(nullptr, fd); nassertd(device != NULL) continue; if (device->has_pointer()) { From 6e476e77a3be854681162ade2488496eed6ff1ca Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 28 Jan 2018 10:58:39 +0100 Subject: [PATCH 52/82] input: support imitation SNES USB gamepad on Windows --- panda/src/device/winInputDeviceManager.cxx | 9 +++- panda/src/device/winRawInputDevice.cxx | 53 ++++++++++++++++------ 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/panda/src/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx index 124229b5ea..8d3d0c769a 100644 --- a/panda/src/device/winInputDeviceManager.cxx +++ b/panda/src/device/winInputDeviceManager.cxx @@ -245,8 +245,15 @@ on_input_device_arrival(HANDLE handle) { if (dev_class == "HIDClass" && _CM_Get_DevNode_PropertyW != nullptr && _CM_Get_DevNode_PropertyW(cur, &bus_reported_device_desc, &type, (PBYTE)buffer, &buflen, 0) == CR_SUCCESS && type == DEVPROP_TYPE_STRING) { + + // Some devices insert quite some trailing space here. + wchar_t *wbuffer = (wchar_t *)buffer; + size_t wlen = wcslen(wbuffer); + while (iswspace(wbuffer[wlen - 1])) { + wbuffer[--wlen] = 0; + } TextEncoder encoder; - name.assign(encoder.encode_wtext((wchar_t *)buffer)); + name.assign(encoder.encode_wtext(wstring(wbuffer, wlen))); break; } else { buflen = 4096; diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index 68e8e89bf3..391140eb31 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -265,6 +265,11 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name) { info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) { _device_class = DC_flight_stick; + if (_name == "usb gamepad") { + // Well, it claims to be a gamepad... + _device_class = DC_gamepad; + } + // Mice } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) { @@ -320,6 +325,39 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name) { return false; } + // Do we have a button mapping? + static const ButtonHandle gamepad_buttons_common[] = { + ButtonHandle::none(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::start(), + GamepadButton::back(), + GamepadButton::lstick(), + GamepadButton::rstick(), + }; + const ButtonHandle *gamepad_buttons = gamepad_buttons_common; + if (_vendor_id == 0x0810 && _product_id == 0xe501) { + // SNES-style USB gamepad + static const ButtonHandle gamepad_buttons_snes[] = { + ButtonHandle::none(), + GamepadButton::action_x(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + ButtonHandle::none(), + ButtonHandle::none(), + GamepadButton::back(), + GamepadButton::start(), + }; + gamepad_buttons = gamepad_buttons_snes; + } + // Prepare a mapping of data indices to button/control indices. _indices.resize(caps.NumberInputDataIndices); @@ -369,20 +407,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name) { switch (cap.UsagePage) { case HID_USAGE_PAGE_BUTTON: if (_device_class == DC_gamepad) { - static const ButtonHandle gamepad_buttons[] = { - ButtonHandle::none(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_x(), - GamepadButton::action_y(), - GamepadButton::lshoulder(), - GamepadButton::rshoulder(), - GamepadButton::start(), - GamepadButton::back(), - GamepadButton::lstick(), - GamepadButton::rstick(), - }; - if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + if (usage < sizeof(gamepad_buttons_common) / sizeof(ButtonHandle)) { handle = gamepad_buttons[usage]; } } else if (_device_class == DC_flight_stick) { From 9e6d0d91adfac589fa4b602ead47e6ecc42abc2d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 4 Feb 2018 10:58:36 +0100 Subject: [PATCH 53/82] input: fixes for older Android API levels (thanks to pmp-p) --- panda/src/device/evdevInputDevice.cxx | 21 ++++++++++++++------ panda/src/device/linuxInputDeviceManager.cxx | 5 +++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index a4bdc43806..cae448c1f3 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -23,6 +23,15 @@ #include #include +// Android introduces these in API level 21. +#ifndef BTN_TRIGGER_HAPPY +#define BTN_TRIGGER_HAPPY 0x2c0 +#define BTN_TRIGGER_HAPPY1 0x2c0 +#define BTN_TRIGGER_HAPPY2 0x2c1 +#define BTN_TRIGGER_HAPPY3 0x2c2 +#define BTN_TRIGGER_HAPPY4 0x2c3 +#endif + #define test_bit(bit, array) ((array)[(bit)>>3] & (1<<((bit)&7))) enum QuirkBits { @@ -220,7 +229,7 @@ init_device() { LightMutexHolder holder(_lock); - uint8_t evtypes[(EV_CNT + 7) >> 3]; + uint8_t evtypes[(EV_MAX + 8) >> 3]; memset(evtypes, 0, sizeof(evtypes)); char name[128]; if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || @@ -248,7 +257,7 @@ init_device() { bool has_keys = false; bool has_axes = false; - uint8_t keys[(KEY_CNT + 7) >> 3]; + uint8_t keys[(KEY_MAX + 8) >> 3]; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. memset(keys, 0, sizeof(keys)); @@ -261,7 +270,7 @@ init_device() { } int num_bits = 0; - uint8_t axes[(ABS_CNT + 7) >> 3]; + uint8_t axes[(ABS_MAX + 8) >> 3]; if (test_bit(EV_ABS, evtypes)) { // Check which axes are on the device. memset(axes, 0, sizeof(axes)); @@ -355,11 +364,11 @@ init_device() { if (has_keys) { // Also check whether the buttons are currently pressed. - uint8_t states[(KEY_CNT + 7) >> 3]; + uint8_t states[(KEY_MAX + 8) >> 3]; memset(states, 0, sizeof(states)); ioctl(_fd, EVIOCGKEY(sizeof(states)), states); - for (int i = 0; i < KEY_CNT; ++i) { + for (int i = 0; i <= KEY_MAX; ++i) { if (test_bit(i, keys)) { ButtonState button; button.handle = map_button(i, _device_class); @@ -546,7 +555,7 @@ init_device() { } if (test_bit(EV_FF, evtypes)) { - uint8_t effects[(FF_CNT + 7) >> 3]; + uint8_t effects[(FF_MAX + 8) >> 3]; memset(effects, 0, sizeof(effects)); ioctl(_fd, EVIOCGBIT(EV_FF, sizeof(effects)), effects); diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 61eed54b42..8278edd226 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -32,7 +32,12 @@ LinuxInputDeviceManager:: LinuxInputDeviceManager() { // Use inotify to watch /dev/input for hotplugging of devices. +#if !defined(__ANDROID_API__) || __ANDROID_API__ >= 21 _inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); +#else + _inotify_fd = inotify_init(); + fcntl(_inotify_fd, O_NONBLOCK | O_CLOEXEC); +#endif if (_inotify_fd < 0) { device_cat.error() From 6ada306184eb17f6bfd9d3d5bcf729aa38cc8805 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 4 Feb 2018 11:00:43 +0100 Subject: [PATCH 54/82] input: split out macOS implementation of device manager --- panda/src/device/inputDeviceManager.cxx | 62 +------------ panda/src/device/inputDeviceManager.h | 16 ---- panda/src/device/ioKitInputDeviceManager.cxx | 93 ++++++++++++++++++++ panda/src/device/ioKitInputDeviceManager.h | 40 +++++++++ panda/src/device/p3device_composite1.cxx | 1 + 5 files changed, 138 insertions(+), 74 deletions(-) create mode 100644 panda/src/device/ioKitInputDeviceManager.cxx create mode 100644 panda/src/device/ioKitInputDeviceManager.h diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index a4ee1c4c8e..140b4379f1 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -12,77 +12,21 @@ */ #include "inputDeviceManager.h" -#include "ioKitInputDevice.h" +#include "ioKitInputDeviceManager.h" #include "linuxInputDeviceManager.h" #include "winInputDeviceManager.h" #include "throw_event.h" -InputDeviceManager *InputDeviceManager::_global_ptr = NULL; +InputDeviceManager *InputDeviceManager::_global_ptr = nullptr; /** * Initializes the input device manager by scanning which devices are currently * connected and setting up any platform-dependent structures necessary for * listening for future device connect events. */ -#if defined(__APPLE__) -InputDeviceManager:: -InputDeviceManager() { - _hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - if (!_hid_manager) { - device_cat.error() - << "Failed to create an IOHIDManager.\n"; - return; - } - - // The types of devices we're interested in. - int page = kHIDPage_GenericDesktop; - int usages[] = {kHIDUsage_GD_GamePad, - kHIDUsage_GD_Joystick, - kHIDUsage_GD_Mouse, - kHIDUsage_GD_Keyboard, - kHIDUsage_GD_MultiAxisController, 0}; - int *usage = usages; - - // This giant mess is necessary to create an array of match dictionaries - // that will match the devices we're interested in. - CFMutableArrayRef match = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); - nassertv(match); - while (*usage) { - CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFNumberRef page_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); - CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), page_ref); - CFRelease(page_ref); - CFNumberRef usage_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, usage); - CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), usage_ref); - CFRelease(usage_ref); - CFArrayAppendValue(match, dict); - CFRelease(dict); - ++usage; - } - IOHIDManagerSetDeviceMatchingMultiple(_hid_manager, match); - CFRelease(match); - - IOHIDManagerRegisterDeviceMatchingCallback(_hid_manager, on_match_device, this); - IOHIDManagerScheduleWithRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); - IOHIDManagerOpen(_hid_manager, kIOHIDOptionsTypeNone); -} -#else InputDeviceManager:: InputDeviceManager() : _lock("InputDeviceManager") { } -#endif - -/** - * Closes any resources that the device manager was using to listen for events. - */ -InputDeviceManager:: -~InputDeviceManager() { -#if defined(__APPLE__) - IOHIDManagerUnscheduleFromRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); - IOHIDManagerClose(_hid_manager, kIOHIDOptionsTypeNone); - CFRelease(_hid_manager); -#endif -} /** * Creates the global input manager. @@ -91,6 +35,8 @@ void InputDeviceManager:: make_global_ptr() { #ifdef _WIN32 _global_ptr = new WinInputDeviceManager; +#elif defined(__APPLE__) + _global_ptr = new IOKitInputDeviceManager; #elif defined(PHAVE_LINUX_INPUT_H) _global_ptr = new LinuxInputDeviceManager; #else diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 5f827cd3a9..8fae76c2d1 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -24,10 +24,6 @@ class WinRawInputDevice; #endif -#ifdef __APPLE__ -#include -#endif - /** * This class keeps track of all the devices on a system, and sends out events * when a device has been hot-plugged. @@ -35,15 +31,9 @@ class WinRawInputDevice; class EXPCL_PANDA_DEVICE InputDeviceManager { protected: InputDeviceManager(); - ~InputDeviceManager(); static void make_global_ptr(); -#ifdef PHAVE_LINUX_INPUT_H - InputDevice *consider_add_evdev_device(int index); - InputDevice *consider_add_js_device(int index); -#endif - PUBLISHED: InputDeviceSet get_devices() const; InputDeviceSet get_devices(InputDevice::DeviceClass device_class) const; @@ -65,12 +55,6 @@ protected: InputDeviceSet _inactive_devices; #endif -#if defined(__APPLE__) && !defined(CPPPARSER) - IOHIDManagerRef _hid_manager; - - static void on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device); -#endif - InputDeviceSet _connected_devices; static InputDeviceManager *_global_ptr; diff --git a/panda/src/device/ioKitInputDeviceManager.cxx b/panda/src/device/ioKitInputDeviceManager.cxx new file mode 100644 index 0000000000..6874d9baba --- /dev/null +++ b/panda/src/device/ioKitInputDeviceManager.cxx @@ -0,0 +1,93 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ioKitInputDeviceManager.cxx + * @author rdb + * @date 2018-02-04 + */ + +#include "ioKitInputDeviceManager.h" +#include "ioKitInputDevice.h" + +#if defined(__APPLE__) && !defined(CPPPARSER) + +/** + * Initializes the input device manager by scanning which devices are currently + * connected and setting up any platform-dependent structures necessary for + * listening for future device connect events. + */ +IOKitInputDeviceManager:: +IOKitInputDeviceManager() { + _hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (!_hid_manager) { + device_cat.error() + << "Failed to create an IOHIDManager.\n"; + return; + } + + // The types of devices we're interested in. + int page = kHIDPage_GenericDesktop; + int usages[] = {kHIDUsage_GD_GamePad, + kHIDUsage_GD_Joystick, + kHIDUsage_GD_Mouse, + kHIDUsage_GD_Keyboard, + kHIDUsage_GD_MultiAxisController, 0}; + int *usage = usages; + + // This giant mess is necessary to create an array of match dictionaries + // that will match the devices we're interested in. + CFMutableArrayRef match = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + nassertv(match); + while (*usage) { + CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFNumberRef page_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), page_ref); + CFRelease(page_ref); + CFNumberRef usage_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, usage); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), usage_ref); + CFRelease(usage_ref); + CFArrayAppendValue(match, dict); + CFRelease(dict); + ++usage; + } + IOHIDManagerSetDeviceMatchingMultiple(_hid_manager, match); + CFRelease(match); + + IOHIDManagerRegisterDeviceMatchingCallback(_hid_manager, on_match_device, this); + IOHIDManagerScheduleWithRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); + IOHIDManagerOpen(_hid_manager, kIOHIDOptionsTypeNone); +} + +/** + * Closes any resources that the device manager was using to listen for events. + */ +IOKitInputDeviceManager:: +~IOKitInputDeviceManager() { + IOHIDManagerUnscheduleFromRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); + IOHIDManagerClose(_hid_manager, kIOHIDOptionsTypeNone); + CFRelease(_hid_manager); +} + +/** + * Called by IOKit when an input device matching our filters has been found. + */ +void IOKitInputDeviceManager:: +on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) { + InputDeviceManager *mgr = (InputDeviceManager *)ctx; + nassertv(mgr != nullptr); + nassertv(device); + + PT(InputDevice) input_device = new IOKitInputDevice(device); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered input device " << *input_device << "\n"; + } + mgr->add_device(input_device); +} + +#endif diff --git a/panda/src/device/ioKitInputDeviceManager.h b/panda/src/device/ioKitInputDeviceManager.h new file mode 100644 index 0000000000..788413e1b0 --- /dev/null +++ b/panda/src/device/ioKitInputDeviceManager.h @@ -0,0 +1,40 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ioKitInputDeviceManager.h + * @author rdb + * @date 2018-02-04 + */ + +#ifndef IOKITINPUTDEVICEMANAGER_H +#define IOKITINPUTDEVICEMANAGER_H + +#include "inputDeviceManager.h" + +#if defined(__APPLE__) && !defined(CPPPARSER) +#include + +/** + * The macOS implementation of InputDeviceManager. + */ +class EXPCL_PANDA_DEVICE IOKitInputDeviceManager FINAL : public InputDeviceManager { +protected: + IOKitInputDeviceManager(); + ~IOKitInputDeviceManager(); + +protected: + IOHIDManagerRef _hid_manager; + + static void on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device); + + friend class InputDeviceManager; +}; + +#endif + +#endif diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 0067579474..6e7da5b78b 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -11,6 +11,7 @@ #include "inputDeviceNode.cxx" #include "inputDeviceSet.cxx" #include "ioKitInputDevice.cxx" +#include "ioKitInputDeviceManager.cxx" #include "linuxInputDeviceManager.cxx" #include "linuxJoystickDevice.cxx" #include "winInputDeviceManager.cxx" From c1fd2e46ded0f7a00da0d13d9658bd78b73d8321 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 4 Feb 2018 11:01:06 +0100 Subject: [PATCH 55/82] input: more device support on macOS (incl. SpaceNavigator) --- panda/src/device/ioKitInputDevice.cxx | 104 +++++++++++++++++++------- 1 file changed, 77 insertions(+), 27 deletions(-) diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index 3b18b3546c..85269d49e6 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -46,8 +46,15 @@ IOKitInputDevice(IOHIDDeviceRef device) : CFStringRef name = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); if (name) { + buffer[0] = 0; CFStringGetCString(name, buffer, sizeof(buffer), kCFStringEncodingUTF8); - _name = buffer; + + // Strip trailing spaces. + size_t len = strlen(buffer); + while (isspace(buffer[len - 1])) { + --len; + } + _name.assign(buffer, len); } CFStringRef mfg = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey)); @@ -89,6 +96,18 @@ IOKitInputDevice(IOHIDDeviceRef device) : } else if (_vendor_id == 0x044f && _product_id == 0xb108) { // T.Flight Hotas X _device_class = DC_flight_stick; + } else if (_vendor_id == 0x046d && + (_product_id == 0xc623 || + _product_id == 0xc625 || + _product_id == 0xc626 || + _product_id == 0xc627 || + _product_id == 0xc628 || + _product_id == 0xc629 || + _product_id == 0xc62b)) { + // 3Dconnexion SpaceNavigator and friends. + _device_class = DC_3d_mouse; + } else if (_name == "usb gamepad") { + _device_class = DC_gamepad; } CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, 0); @@ -189,15 +208,25 @@ parse_element(IOHIDElementRef element) { case kHIDUsage_GD_Z: if (_device_class == DC_gamepad) { axis = C_left_trigger; - } else { + } else if (_device_class == DC_flight_stick) { axis = C_throttle; + } else { + axis = C_z; } break; case kHIDUsage_GD_Rx: - axis = C_right_x; + if (_device_class == DC_gamepad) { + axis = C_right_x; + } else { + axis = C_pitch; + } break; case kHIDUsage_GD_Ry: - axis = C_right_y; + if (_device_class == DC_gamepad) { + axis = C_right_y; + } else { + axis = C_roll; + } break; case kHIDUsage_GD_Rz: if (_device_class == DC_gamepad) { @@ -259,7 +288,8 @@ parse_element(IOHIDElementRef element) { if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. add_control(axis, max, min, true); - } else if (axis == C_yaw || axis == C_left_y || axis == C_right_y) { + } else if (axis == C_yaw || axis == C_rudder || axis == C_left_y || axis == C_right_y || + (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z || axis == C_roll))) { // We'd like to reverse the Y axis to match the XInput behavior. // We also reverse yaw to obey the right-hand rule. add_control(axis, max, min); @@ -598,28 +628,48 @@ parse_element(IOHIDElementRef element) { case kHIDPage_Button: if (_device_class == DC_gamepad) { - // These seem to be the button mappings exposed by the 360Controller - // driver I don't know if other drivers do the same thing at all. - static const ButtonHandle gamepad_buttons[] = { - ButtonHandle::none(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_x(), - GamepadButton::action_y(), - GamepadButton::lshoulder(), - GamepadButton::rshoulder(), - GamepadButton::lstick(), - GamepadButton::rstick(), - GamepadButton::start(), - GamepadButton::back(), - GamepadButton::guide(), - GamepadButton::dpad_up(), - GamepadButton::dpad_down(), - GamepadButton::dpad_left(), - GamepadButton::dpad_right(), - }; - if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { - handle = gamepad_buttons[usage]; + if (_vendor_id == 0x0810 && _product_id == 0xe501) { + // SNES-style USB gamepad + static const ButtonHandle gamepad_buttons[] = { + ButtonHandle::none(), + GamepadButton::action_x(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + ButtonHandle::none(), + ButtonHandle::none(), + GamepadButton::back(), + GamepadButton::start(), + }; + if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + handle = gamepad_buttons[usage]; + } + } else { + // These seem to be the button mappings exposed by the 360Controller + // driver. I don't know if other drivers do the same thing at all. + static const ButtonHandle gamepad_buttons[] = { + ButtonHandle::none(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::lstick(), + GamepadButton::rstick(), + GamepadButton::start(), + GamepadButton::back(), + GamepadButton::guide(), + GamepadButton::dpad_up(), + GamepadButton::dpad_down(), + GamepadButton::dpad_left(), + GamepadButton::dpad_right(), + }; + if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + handle = gamepad_buttons[usage]; + } } } else if (_device_class == DC_flight_stick) { if (usage > 0) { From 77f33f36421284eaa3109a005186cfd3322acfda Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 4 Apr 2018 21:32:44 +0200 Subject: [PATCH 56/82] device: never use inotify_init1(), it is not in older kernels --- panda/src/device/linuxInputDeviceManager.cxx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 8278edd226..114ac144e3 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -32,12 +32,8 @@ LinuxInputDeviceManager:: LinuxInputDeviceManager() { // Use inotify to watch /dev/input for hotplugging of devices. -#if !defined(__ANDROID_API__) || __ANDROID_API__ >= 21 - _inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); -#else _inotify_fd = inotify_init(); fcntl(_inotify_fd, O_NONBLOCK | O_CLOEXEC); -#endif if (_inotify_fd < 0) { device_cat.error() From ff667282126373dd4753a86aa0def0a02ef86e61 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 4 Apr 2018 19:11:03 -0700 Subject: [PATCH 57/82] LinuxInputDeviceManager: Fix fcntl() call --- panda/src/device/linuxInputDeviceManager.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 114ac144e3..2b19502592 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -33,7 +33,8 @@ LinuxInputDeviceManager:: LinuxInputDeviceManager() { // Use inotify to watch /dev/input for hotplugging of devices. _inotify_fd = inotify_init(); - fcntl(_inotify_fd, O_NONBLOCK | O_CLOEXEC); + fcntl(_inotify_fd, F_SETFL, O_NONBLOCK); + fcntl(_inotify_fd, F_SETFD, FD_CLOEXEC); if (_inotify_fd < 0) { device_cat.error() From 664d628cbcbf2428bf6158eb940ca1e4b6a97b75 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 5 Apr 2018 21:04:40 +0200 Subject: [PATCH 58/82] device: D-Pad mappings for DualShock 3 driver on Linux --- panda/src/device/evdevInputDevice.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index cae448c1f3..5e2be61a95 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -971,15 +971,19 @@ map_button(int code, DeviceClass device_class) { case BTN_THUMBR: return GamepadButton::rstick(); + case BTN_DPAD_LEFT: case BTN_TRIGGER_HAPPY1: return GamepadButton::dpad_left(); + case BTN_DPAD_RIGHT: case BTN_TRIGGER_HAPPY2: return GamepadButton::dpad_right(); + case BTN_DPAD_UP: case BTN_TRIGGER_HAPPY3: return GamepadButton::dpad_up(); + case BTN_DPAD_DOWN: case BTN_TRIGGER_HAPPY4: return GamepadButton::dpad_down(); From aaf7ff9a6ce87ef0ed8817cf9808abf04ea72b1b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 5 May 2018 18:10:05 -0700 Subject: [PATCH 59/82] device: Define BTN_DPAD* if not present --- panda/src/device/evdevInputDevice.cxx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 5e2be61a95..cf0cc18cc6 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -23,6 +23,14 @@ #include #include +#ifndef BTN_DPAD_UP +#define BTN_DPAD_UP 0x220 +#define BTN_DPAD_DOWN 0x221 +#define BTN_DPAD_LEFT 0x222 +#define BTN_DPAD_RIGHT 0x223 +#endif + + // Android introduces these in API level 21. #ifndef BTN_TRIGGER_HAPPY #define BTN_TRIGGER_HAPPY 0x2c0 From 11723b27ee92b69341abed8c38d62144aa6d1ae8 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 23 Jun 2018 23:30:44 +0200 Subject: [PATCH 60/82] device: various warning fixes for input-overhaul on Linux --- panda/src/device/evdevInputDevice.cxx | 24 +++++++------- panda/src/device/evdevInputDevice.h | 4 +-- panda/src/device/linuxInputDeviceManager.cxx | 33 ++++++++++---------- panda/src/device/linuxInputDeviceManager.h | 4 +-- panda/src/device/linuxJoystickDevice.cxx | 18 +++++------ panda/src/device/linuxJoystickDevice.h | 4 +-- 6 files changed, 43 insertions(+), 44 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index e4ca7592b2..dec94f803d 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -96,7 +96,7 @@ TypeHandle EvdevInputDevice::_type_handle; * Creates a new device representing the evdev device with the given index. */ EvdevInputDevice:: -EvdevInputDevice(LinuxInputDeviceManager *manager, int index) : +EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index) : _manager(manager), _index(index), _fd(-1), @@ -113,7 +113,7 @@ EvdevInputDevice(LinuxInputDeviceManager *manager, int index) : _rtrigger_code(-1) { char path[64]; - sprintf(path, "/dev/input/event%d", index); + sprintf(path, "/dev/input/event%zd", index); _fd = open(path, O_RDWR | O_NONBLOCK); if (_fd >= 0) { @@ -338,8 +338,8 @@ init_device() { // Test for specific name tags string lowercase_name = _name; - for(int x=0; x<_name.length(); x++) { - lowercase_name[x]=tolower(lowercase_name[x]); + for(size_t x = 0; x < _name.length(); ++x) { + lowercase_name[x] = tolower(lowercase_name[x]); } if (lowercase_name.find("gamepad") != string::npos) { device_scores[DC_gamepad] += 10; @@ -362,7 +362,7 @@ init_device() { } // Check which device type got the most points - size_t highest_score = 0; + int highest_score = 0; for (size_t i = 0; i < DC_COUNT; i++) { if (device_scores[i] > highest_score) { highest_score = device_scores[i]; @@ -406,7 +406,7 @@ init_device() { } _buttons.push_back(button); - if (i >= _button_indices.size()) { + if ((size_t)i >= _button_indices.size()) { _button_indices.resize(i + 1, -1); } _button_indices[i] = button_index; @@ -594,11 +594,11 @@ init_device() { char path[64]; char buffer[256]; const char *parent = ""; - sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); + sprintf(path, "/sys/class/input/event%zd/device/device/../product", _index); FILE *f = fopen(path, "r"); if (!f) { parent = "../"; - sprintf(path, "/sys/class/input/event%d/device/device/%s../product", _index, parent); + sprintf(path, "/sys/class/input/event%zd/device/device/%s../product", _index, parent); f = fopen(path, "r"); } if (f) { @@ -610,7 +610,7 @@ init_device() { } fclose(f); } - sprintf(path, "/sys/class/input/event%d/device/device/%s../manufacturer", _index, parent); + sprintf(path, "/sys/class/input/event%zd/device/device/%s../manufacturer", _index, parent); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -619,7 +619,7 @@ init_device() { } fclose(f); } - sprintf(path, "/sys/class/input/event%d/device/device/%s../serial", _index, parent); + sprintf(path, "/sys/class/input/event%zd/device/device/%s../serial", _index, parent); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -709,7 +709,7 @@ process_events() { button_changed(_dpad_up_button, events[i].value < 0); button_changed(_dpad_up_button+1, events[i].value > 0); } - nassertd(code >= 0 && code < _control_indices.size()) break; + nassertd(code >= 0 && (size_t)code < _control_indices.size()) break; index = _control_indices[code]; if (index >= 0) { control_changed(index, events[i].value); @@ -717,7 +717,7 @@ process_events() { break; case EV_KEY: - nassertd(code >= 0 && code < _button_indices.size()) break; + nassertd(code >= 0 && (size_t)code < _button_indices.size()) break; index = _button_indices[code]; if (index >= 0) { button_changed(index, events[i].value != 0); diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 8f73d04a20..d82ba5f980 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -27,7 +27,7 @@ class LinuxInputDeviceManager; */ class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { public: - EvdevInputDevice(LinuxInputDeviceManager *manager, int index); + EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index); virtual ~EvdevInputDevice(); private: @@ -41,7 +41,7 @@ private: LinuxInputDeviceManager *_manager; int _fd; - int _index; + size_t _index; bool _can_write; int _ff_id; diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 4591484b11..2e956cb739 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -48,11 +48,11 @@ LinuxInputDeviceManager() { // Scan /dev/input for a list of input devices. DIR *dir = opendir("/dev/input"); if (dir) { - vector_int indices; + std::vector indices; dirent *entry = readdir(dir); while (entry != nullptr) { - int index = -1; - if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) { + size_t index; + if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%zd", &index) == 1) { indices.push_back(index); } entry = readdir(dir); @@ -64,9 +64,8 @@ LinuxInputDeviceManager() { std::sort(indices.begin(), indices.end()); _evdev_devices.resize(indices.back() + 1, nullptr); - vector_int::const_iterator it; - for (it = indices.begin(); it != indices.end(); ++it) { - consider_add_evdev_device(*it); + for (size_t index : indices) { + consider_add_evdev_device(index); } } else { device_cat.error() @@ -93,7 +92,7 @@ LinuxInputDeviceManager:: * This is the preferred interface for input devices on Linux. */ InputDevice *LinuxInputDeviceManager:: -consider_add_evdev_device(int ev_index) { +consider_add_evdev_device(size_t ev_index) { if (ev_index < _evdev_devices.size()) { if (_evdev_devices[ev_index] != nullptr) { // We already have this device. FIXME: probe it and add it to the @@ -107,7 +106,7 @@ consider_add_evdev_device(int ev_index) { // Check if we can directly read the event device. char path[64]; - sprintf(path, "/dev/input/event%d", ev_index); + sprintf(path, "/dev/input/event%zd", ev_index); if (access(path, R_OK) == 0) { PT(InputDevice) device = new EvdevInputDevice(this, ev_index); @@ -133,7 +132,7 @@ consider_add_evdev_device(int ev_index) { // (notably, force feedback). // We do this by checking for a js# directory inside the sysfs directory. - sprintf(path, "/sys/class/input/event%d/device", ev_index); + sprintf(path, "/sys/class/input/event%zd/device", ev_index); DIR *dir = opendir(path); if (dir == nullptr) { @@ -146,8 +145,8 @@ consider_add_evdev_device(int ev_index) { dirent *entry = readdir(dir); while (entry != nullptr) { - int js_index = -1; - if (sscanf(entry->d_name, "js%d", &js_index) == 1) { + size_t js_index; + if (sscanf(entry->d_name, "js%zd", &js_index) == 1) { // Yes, we fonud a corresponding js device. Try adding it. closedir(dir); @@ -177,9 +176,9 @@ consider_add_evdev_device(int ev_index) { * cannot be accessed. */ InputDevice *LinuxInputDeviceManager:: -consider_add_js_device(int js_index) { +consider_add_js_device(size_t js_index) { char path[64]; - sprintf(path, "/dev/input/js%d", js_index); + sprintf(path, "/dev/input/js%zd", js_index); if (access(path, R_OK) == 0) { PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(this, js_index); @@ -254,8 +253,8 @@ update() { if (event->mask & IN_DELETE) { // The device was deleted. If we have it, remove it. - int index = -1; - if (sscanf(event->name, "event%d", &index) == 1) { + size_t index; + if (sscanf(event->name, "event%zd", &index) == 1) { // Check if we have this evdev device. If so, disconnect it. if (index < _evdev_devices.size()) { PT(InputDevice) device = _evdev_devices[index]; @@ -280,8 +279,8 @@ update() { // to check for the latter since it seems that the device may get the // IN_CREATE event before the driver gets the permissions set properly. - int index = -1; - if (sscanf(event->name, "event%d", &index) == 1) { + size_t index; + if (sscanf(event->name, "event%zd", &index) == 1) { InputDevice *device = consider_add_evdev_device(index); if (device != nullptr && device->is_connected()) { throw_event("connect-device", device); diff --git a/panda/src/device/linuxInputDeviceManager.h b/panda/src/device/linuxInputDeviceManager.h index 61d0bf386a..c5ad6a013f 100644 --- a/panda/src/device/linuxInputDeviceManager.h +++ b/panda/src/device/linuxInputDeviceManager.h @@ -27,8 +27,8 @@ private: LinuxInputDeviceManager(); ~LinuxInputDeviceManager(); - InputDevice *consider_add_evdev_device(int index); - InputDevice *consider_add_js_device(int index); + InputDevice *consider_add_evdev_device(size_t index); + InputDevice *consider_add_js_device(size_t index); virtual void update(); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 5a82197f06..6617fc1dcd 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -28,7 +28,7 @@ TypeHandle LinuxJoystickDevice::_type_handle; * Creates a new device using the Linux joystick device with the given index. */ LinuxJoystickDevice:: -LinuxJoystickDevice(LinuxInputDeviceManager *manager, int index) : +LinuxJoystickDevice(LinuxInputDeviceManager *manager, size_t index) : _manager(manager), _fd(-1), _index(index), @@ -97,7 +97,7 @@ open_device() { nassertr(_lock.debug_is_locked(), false); char path[64]; - sprintf(path, "/dev/input/js%d", _index); + sprintf(path, "/dev/input/js%zd", _index); _fd = open(path, O_RDONLY | O_NONBLOCK); @@ -287,7 +287,7 @@ open_device() { } // Get additional information from sysfs. - sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index); + sprintf(path, "/sys/class/input/js%zd/device/id/vendor", _index); FILE *f = fopen(path, "r"); if (f) { if (fscanf(f, "%hx", &_vendor_id) < 1) { @@ -295,7 +295,7 @@ open_device() { } fclose(f); } - sprintf(path, "/sys/class/input/js%d/device/id/product", _index); + sprintf(path, "/sys/class/input/js%zd/device/id/product", _index); f = fopen(path, "r"); if (f) { if (fscanf(f, "%hx", &_product_id) < 1) { @@ -304,7 +304,7 @@ open_device() { fclose(f); } char buffer[256]; - sprintf(path, "/sys/class/input/js%d/device/device/../product", _index); + sprintf(path, "/sys/class/input/js%zd/device/device/../product", _index); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -315,7 +315,7 @@ open_device() { } fclose(f); } - sprintf(path, "/sys/class/input/js%d/device/device/../manufacturer", _index); + sprintf(path, "/sys/class/input/js%zd/device/device/../manufacturer", _index); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -324,7 +324,7 @@ open_device() { } fclose(f); } - sprintf(path, "/sys/class/input/js%d/device/device/../serial", _index); + sprintf(path, "/sys/class/input/js%zd/device/device/../serial", _index); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -343,8 +343,8 @@ open_device() { // are all 0, which indicates that the driver hasn't received any data for // this gamepad yet (which means it hasn't been plugged in for this session) if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) { - for (int i = 0; i < _controls.size(); ++i) { - if (_controls[i].state != 0.0) { + for (const auto &control : _controls) { + if (control.state != 0.0) { _is_connected = true; return true; } diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index fad69730c1..af0a74e3e6 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -26,7 +26,7 @@ class LinuxInputDeviceManager; */ class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice { PUBLISHED: - LinuxJoystickDevice(LinuxInputDeviceManager *manager, int index); + LinuxJoystickDevice(LinuxInputDeviceManager *manager, size_t index); virtual ~LinuxJoystickDevice(); bool check_events() const; @@ -41,7 +41,7 @@ private: LinuxInputDeviceManager *_manager; int _fd; - int _index; + size_t _index; // These are used for D-pad / hat switch emulation. int _dpad_x_axis; From aae2b1e5ad5ea582ee5895e136482986e0b3ae34 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 26 Aug 2018 21:18:11 +0200 Subject: [PATCH 61/82] putil: fix a compiler warning --- panda/src/putil/gamepadButton.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index fc580e6b5e..12fae57b50 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -63,7 +63,7 @@ joystick(int button_number) { if (button_number >= 0) { // "button1" does not exist, it is called "trigger" instead static pvector buttons(1, _trigger); - while (button_number >= buttons.size()) { + while ((size_t)button_number >= buttons.size()) { char numstr[20]; sprintf(numstr, "joystick%d", (int)buttons.size() + 1); ButtonHandle handle; From 7cc100c38c97ba9b27508e173aff189f73a29012 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 26 Aug 2018 21:18:29 +0200 Subject: [PATCH 62/82] device: change ControlAxis enum to an Axis enum class --- panda/src/device/evdevInputDevice.cxx | 64 ++++++++--------- panda/src/device/inputDevice.I | 39 +++-------- panda/src/device/inputDevice.cxx | 74 ++++++++++---------- panda/src/device/inputDevice.h | 70 +++++++++--------- panda/src/device/ioKitInputDevice.cxx | 50 ++++++------- panda/src/device/linuxInputDeviceManager.cxx | 8 +-- panda/src/device/linuxJoystickDevice.cxx | 50 ++++++------- panda/src/device/winRawInputDevice.cxx | 38 +++++----- panda/src/device/xInputDevice.cxx | 48 ++++++------- 9 files changed, 209 insertions(+), 232 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index dec94f803d..cf15b50f2a 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -419,89 +419,89 @@ init_device() { for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { - ControlAxis axis = C_none; + Axis axis = Axis::none; switch (i) { case ABS_X: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_x; + axis = InputDevice::Axis::left_x; } else if (_device_class == DC_flight_stick) { - axis = InputDevice::C_roll; + axis = InputDevice::Axis::roll; } else { - axis = InputDevice::C_x; + axis = InputDevice::Axis::x; } break; case ABS_Y: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_y; + axis = InputDevice::Axis::left_y; } else if (_device_class == DC_flight_stick) { - axis = InputDevice::C_pitch; + axis = InputDevice::Axis::pitch; } else { - axis = InputDevice::C_y; + axis = InputDevice::Axis::y; } break; case ABS_Z: if (quirks & QB_rstick_from_z) { - axis = InputDevice::C_right_x; + axis = InputDevice::Axis::right_x; } else if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_trigger; + axis = InputDevice::Axis::left_trigger; have_analog_triggers = true; } else if (_device_class == DC_3d_mouse) { - axis = InputDevice::C_z; + axis = InputDevice::Axis::z; } else { - axis = InputDevice::C_throttle; + axis = InputDevice::Axis::throttle; } break; case ABS_RX: if (_device_class == DC_3d_mouse) { - axis = InputDevice::C_pitch; + axis = InputDevice::Axis::pitch; } else if ((quirks & QB_rstick_from_z) == 0) { - axis = InputDevice::C_right_x; + axis = InputDevice::Axis::right_x; } break; case ABS_RY: if (_device_class == DC_3d_mouse) { - axis = InputDevice::C_roll; + axis = InputDevice::Axis::roll; } else if ((quirks & QB_rstick_from_z) == 0) { - axis = InputDevice::C_right_y; + axis = InputDevice::Axis::right_y; } break; case ABS_RZ: if (quirks & QB_rstick_from_z) { - axis = InputDevice::C_right_y; + axis = InputDevice::Axis::right_y; } else if (_device_class == DC_gamepad) { - axis = InputDevice::C_right_trigger; + axis = InputDevice::Axis::right_trigger; have_analog_triggers = true; } else { - axis = InputDevice::C_yaw; + axis = InputDevice::Axis::yaw; } break; case ABS_THROTTLE: if (quirks & QB_rudder_from_throttle) { - axis = InputDevice::C_rudder; + axis = InputDevice::Axis::rudder; } else { - axis = InputDevice::C_throttle; + axis = InputDevice::Axis::throttle; } break; case ABS_RUDDER: - axis = InputDevice::C_rudder; + axis = InputDevice::Axis::rudder; break; case ABS_WHEEL: - axis = InputDevice::C_wheel; + axis = InputDevice::Axis::wheel; break; case ABS_GAS: if (_device_class == DC_gamepad) { - axis = InputDevice::C_right_trigger; + axis = InputDevice::Axis::right_trigger; have_analog_triggers = true; } else { - axis = InputDevice::C_accelerator; + axis = InputDevice::Axis::accelerator; } break; case ABS_BRAKE: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_trigger; + axis = InputDevice::Axis::left_trigger; have_analog_triggers = true; } else { - axis = InputDevice::C_brake; + axis = InputDevice::Axis::brake; } break; case ABS_HAT0X: @@ -539,12 +539,12 @@ init_device() { // We'd like to reverse the Y axis to match the XInput behavior. // Also reverse the yaw axis to match right-hand coordinate system. // Also T.Flight Hotas X throttle is reversed and can go backwards. - if (axis == C_yaw || axis == C_rudder || axis == C_left_y || axis == C_right_y || - (axis == C_throttle && (quirks & QB_reversed_throttle) != 0) || - (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z || axis == C_roll))) { + if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || + (axis == Axis::throttle && (quirks & QB_reversed_throttle) != 0) || + (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { std::swap(absinfo.maximum, absinfo.minimum); } - if (axis == C_throttle && (quirks & QB_centered_throttle) != 0) { + if (axis == Axis::throttle && (quirks & QB_centered_throttle) != 0) { index = add_control(axis, absinfo.maximum, absinfo.minimum, true); } else { index = add_control(axis, absinfo.minimum, absinfo.maximum); @@ -584,8 +584,8 @@ init_device() { if (_ltrigger_code >= 0 && _rtrigger_code >= 0 && !have_analog_triggers) { // Emulate analog triggers. _ltrigger_control = (int)_controls.size(); - add_control(C_left_trigger, 0, 1, false); - add_control(C_right_trigger, 0, 1, false); + add_control(Axis::left_trigger, 0, 1, false); + add_control(Axis::right_trigger, 0, 1, false); } else { _ltrigger_code = -1; _rtrigger_code = -1; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 43c71fd1eb..a8eb5a127b 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -292,14 +292,14 @@ get_num_controls() const { } /** - * Associates the indicated ControlAxis with the control of the indicated index - * number. Pass C_none to turn off any association. + * Associates the indicated Axis with the control of the indicated index + * number. Pass Axis::none to turn off any association. * * It is not necessary to call this if you simply want to query the state of * the various controls by index number. */ INLINE void InputDevice:: -set_control_map(size_t index, InputDevice::ControlAxis axis) { +set_control_map(size_t index, InputDevice::Axis axis) { LightMutexHolder holder(_lock); if (index >= _controls.size()) { _controls.resize(index + 1, AnalogState()); @@ -309,16 +309,16 @@ set_control_map(size_t index, InputDevice::ControlAxis axis) { } /** - * Returns the ControlAxis that was previously associated with the given index - * number by a call to set_control_map(), or C_none if no control was + * Returns the Axis that was previously associated with the given index + * number by a call to set_control_map(), or Axis::none if no control was * associated. */ -INLINE InputDevice::ControlAxis InputDevice:: +INLINE InputDevice::Axis InputDevice:: get_control_map(size_t index) const { if (index < _controls.size()) { return _controls[index].axis; } else { - return C_none; + return Axis::none; } } @@ -356,7 +356,7 @@ get_control(size_t index) const { * if the axis was not found in the list. */ INLINE InputDevice::AnalogState InputDevice:: -find_control(InputDevice::ControlAxis axis) const { +find_control(InputDevice::Axis axis) const { for (size_t i = 0; i < _controls.size(); ++i) { if (_controls[i].axis == axis) { return _controls[i]; @@ -444,16 +444,6 @@ operator < (const InputDevice &) const { return false; } -/** - * - */ -INLINE InputDevice::ButtonState:: -ButtonState() : - handle(ButtonHandle::none()), - state(S_unknown) -{ -} - /** * */ @@ -463,16 +453,3 @@ ButtonState(ButtonHandle handle) : state(S_unknown) { } - -/** - * - */ -INLINE InputDevice::AnalogState:: -AnalogState() : - axis(C_none), - state(0.0), - known(false), - _scale(1.0), - _bias(0.0) -{ -} diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 00e3e0056c..e24cd9a0df 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -124,7 +124,7 @@ get_pointer_events() { * Called by the implementation to add a new known control. */ int InputDevice:: -add_control(ControlAxis axis, int minimum, int maximum, bool centered) { +add_control(Axis axis, int minimum, int maximum, bool centered) { AnalogState state; state.axis = axis; if (centered) { @@ -146,20 +146,20 @@ add_control(ControlAxis axis, int minimum, int maximum, bool centered) { * tries to guess whether the control is centered or not. */ int InputDevice:: -add_control(ControlAxis axis, int minimum, int maximum) { +add_control(Axis axis, int minimum, int maximum) { bool centered = (minimum < 0) - || axis == C_x - || axis == C_y - || axis == C_z - || axis == C_yaw - || axis == C_pitch - || axis == C_roll - || axis == C_left_x - || axis == C_left_y - || axis == C_right_x - || axis == C_right_y - || axis == C_wheel - || axis == C_rudder; + || axis == Axis::x + || axis == Axis::y + || axis == Axis::z + || axis == Axis::yaw + || axis == Axis::pitch + || axis == Axis::roll + || axis == Axis::left_x + || axis == Axis::left_y + || axis == Axis::right_x + || axis == Axis::right_y + || axis == Axis::wheel + || axis == Axis::rudder; return add_control(axis, minimum, maximum, centered); } @@ -287,7 +287,7 @@ set_control_state(int index, double state) { device_cat.spam() << "Changed control " << index; - if (_controls[index].axis != C_none) { + if (_controls[index].axis != Axis::none) { device_cat.spam(false) << " (" << _controls[index].axis << ")"; } @@ -315,7 +315,7 @@ control_changed(int index, int state) { device_cat.spam() << "Changed control " << index; - if (_controls[index].axis != C_none) { + if (_controls[index].axis != Axis::none) { device_cat.spam(false) << " (" << _controls[index].axis << ")"; } @@ -556,63 +556,63 @@ format_device_class(DeviceClass dc) { * Returns a string describing the given axis enumerant. */ std::string InputDevice:: -format_axis(ControlAxis axis) { +format_axis(Axis axis) { switch (axis) { - case InputDevice::C_none: + case InputDevice::Axis::none: return "none"; - case InputDevice::C_x: + case InputDevice::Axis::x: return "x"; - case InputDevice::C_y: + case InputDevice::Axis::y: return "y"; - case InputDevice::C_z: + case InputDevice::Axis::z: return "z"; - case InputDevice::C_yaw: + case InputDevice::Axis::yaw: return "yaw"; - case InputDevice::C_pitch: + case InputDevice::Axis::pitch: return "pitch"; - case InputDevice::C_roll: + case InputDevice::Axis::roll: return "roll"; - case InputDevice::C_left_x: + case InputDevice::Axis::left_x: return "left_x"; - case InputDevice::C_left_y: + case InputDevice::Axis::left_y: return "left_y"; - case InputDevice::C_left_trigger: + case InputDevice::Axis::left_trigger: return "left_trigger"; - case InputDevice::C_right_x: + case InputDevice::Axis::right_x: return "right_x"; - case InputDevice::C_right_y: + case InputDevice::Axis::right_y: return "right_y"; - case InputDevice::C_right_trigger: + case InputDevice::Axis::right_trigger: return "right_trigger"; - //case InputDevice::C_trigger: + //case InputDevice::Axis::trigger: // return "trigger"; - case InputDevice::C_throttle: + case InputDevice::Axis::throttle: return "throttle"; - case InputDevice::C_rudder: + case InputDevice::Axis::rudder: return "rudder"; - case InputDevice::C_wheel: + case InputDevice::Axis::wheel: return "wheel"; - case InputDevice::C_accelerator: + case InputDevice::Axis::accelerator: return "accelerator"; - case InputDevice::C_brake: + case InputDevice::Axis::brake: return "brake"; } return "**invalid**"; @@ -625,7 +625,7 @@ operator << (std::ostream &out, InputDevice::DeviceClass dc) { } std::ostream & -operator << (std::ostream &out, InputDevice::ControlAxis axis) { +operator << (std::ostream &out, InputDevice::Axis axis) { out << InputDevice::format_axis(axis); return out; } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index fd113a0852..77b42b43c0 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -95,35 +95,35 @@ public: ~InputDevice(); PUBLISHED: - enum ControlAxis { - C_none, + enum class Axis { + none, // Generic translational axes - C_x, - C_y, - C_z, + x, + y, + z, // Generic rotational axes, used by joysticks and 3D mice - C_yaw, - C_pitch, - C_roll, + yaw, + pitch, + roll, // Gamepad - C_left_x, - C_left_y, - C_left_trigger, - C_right_x, - C_right_y, - C_right_trigger, + left_x, + left_y, + left_trigger, + right_x, + right_y, + right_trigger, // Flight stick specific - C_throttle, - C_rudder, // When available separately from yaw + throttle, + rudder, // When available separately from yaw // Steering wheel / pedals - C_wheel, - C_accelerator, - C_brake, + wheel, + accelerator, + brake, }; INLINE std::string get_name() const; @@ -172,8 +172,8 @@ PUBLISHED: INLINE bool is_button_known(size_t index) const; INLINE size_t get_num_controls() const; - INLINE void set_control_map(size_t index, ControlAxis axis); - INLINE ControlAxis get_control_map(size_t index) const; + INLINE void set_control_map(size_t index, Axis axis); + INLINE Axis get_control_map(size_t index) const; INLINE double get_control_state(size_t index) const; INLINE bool is_control_known(size_t index) const; @@ -191,12 +191,12 @@ PUBLISHED: virtual void output(std::ostream &out) const; static std::string format_device_class(DeviceClass dc); - static std::string format_axis(ControlAxis axis); + static std::string format_axis(Axis axis); protected: // Called during the constructor to add new controls or buttons - int add_control(ControlAxis axis, int minimum, int maximum, bool centered); - int add_control(ControlAxis axis, int minimum, int maximum); + int add_control(Axis axis, int minimum, int maximum, bool centered); + int add_control(Axis axis, int minimum, int maximum); void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); @@ -271,28 +271,28 @@ PUBLISHED: class ButtonState { public: - INLINE ButtonState(); + constexpr ButtonState() = default; INLINE ButtonState(ButtonHandle handle); PUBLISHED: - ButtonHandle handle; - State state; + ButtonHandle handle = ButtonHandle::none(); + State state = S_unknown; }; typedef pvector Buttons; Buttons _buttons; class AnalogState { public: - INLINE AnalogState(); + constexpr AnalogState() = default; PUBLISHED: - ControlAxis axis; - double state; - bool known; + Axis axis = Axis::none; + double state = 0.0; + bool known = false; public: - double _scale; - double _bias; + double _scale = 1.0; + double _bias = 0.0; }; typedef pvector Controls; Controls _controls; @@ -307,7 +307,7 @@ PUBLISHED: INLINE ButtonState find_button(ButtonHandle handle) const; INLINE AnalogState get_control(size_t index) const; - INLINE AnalogState find_control(ControlAxis axis) const; + INLINE AnalogState find_control(Axis axis) const; // Make device buttons and controls iterable MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button); @@ -337,7 +337,7 @@ INLINE std::ostream &operator << (std::ostream &out, const InputDevice &device) } EXPCL_PANDA_DEVICE std::ostream &operator << (std::ostream &out, InputDevice::DeviceClass dc); -EXPCL_PANDA_DEVICE std::ostream &operator << (std::ostream &out, InputDevice::ControlAxis axis); +EXPCL_PANDA_DEVICE std::ostream &operator << (std::ostream &out, InputDevice::Axis axis); #include "inputDevice.I" diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index 5854fc3015..3313e3f78a 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -172,7 +172,7 @@ on_remove() { void IOKitInputDevice:: parse_element(IOHIDElementRef element) { ButtonHandle handle = ButtonHandle::none(); - ControlAxis axis = C_none; + Axis axis = Axis::none; uint32_t page = IOHIDElementGetUsagePage(element); uint32_t usage = IOHIDElementGetUsage(element); @@ -183,60 +183,60 @@ parse_element(IOHIDElementRef element) { switch (usage) { case kHIDUsage_GD_X: if (_device_class == DC_gamepad) { - axis = C_left_x; + axis = Axis::left_x; } else if (_device_class == DC_flight_stick) { - axis = C_roll; + axis = Axis::roll; } else if (_device_class == DC_mouse) { _pointer_x = element; return; } else { - axis = C_x; + axis = Axis::x; } break; case kHIDUsage_GD_Y: if (_device_class == DC_gamepad) { - axis = C_left_y; + axis = Axis::left_y; } else if (_device_class == DC_flight_stick) { - axis = C_pitch; + axis = Axis::pitch; } else if (_device_class == DC_mouse) { _pointer_y = element; return; } else { - axis = C_y; + axis = Axis::y; } break; case kHIDUsage_GD_Z: if (_device_class == DC_gamepad) { - axis = C_left_trigger; + axis = Axis::left_trigger; } else if (_device_class == DC_flight_stick) { - axis = C_throttle; + axis = Axis::throttle; } else { - axis = C_z; + axis = Axis::z; } break; case kHIDUsage_GD_Rx: if (_device_class == DC_gamepad) { - axis = C_right_x; + axis = Axis::right_x; } else { - axis = C_pitch; + axis = Axis::pitch; } break; case kHIDUsage_GD_Ry: if (_device_class == DC_gamepad) { - axis = C_right_y; + axis = Axis::right_y; } else { - axis = C_roll; + axis = Axis::roll; } break; case kHIDUsage_GD_Rz: if (_device_class == DC_gamepad) { - axis = C_right_trigger; + axis = Axis::right_trigger; } else { - axis = C_yaw; + axis = Axis::yaw; } break; case kHIDUsage_GD_Slider: - axis = C_rudder; + axis = Axis::rudder; break; case kHIDUsage_GD_Dial: break; @@ -268,28 +268,28 @@ parse_element(IOHIDElementRef element) { case kHIDPage_Simulation: switch (usage) { case kHIDUsage_Sim_Rudder: - axis = C_rudder; + axis = Axis::rudder; break; case kHIDUsage_Sim_Throttle: - axis = C_throttle; + axis = Axis::throttle; break; case kHIDUsage_Sim_Accelerator: - axis = C_accelerator; + axis = Axis::accelerator; break; case kHIDUsage_Sim_Brake: - axis = C_brake; + axis = Axis::brake; break; } break; } - if (axis != C_none) { + if (axis != Axis::none) { int min = IOHIDElementGetLogicalMin(element); int max = IOHIDElementGetLogicalMax(element); - if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { + if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. add_control(axis, max, min, true); - } else if (axis == C_yaw || axis == C_rudder || axis == C_left_y || axis == C_right_y || - (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z || axis == C_roll))) { + } else if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || + (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { // We'd like to reverse the Y axis to match the XInput behavior. // We also reverse yaw to obey the right-hand rule. add_control(axis, max, min); diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 2e956cb739..ce7ce558e2 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -118,10 +118,10 @@ consider_add_evdev_device(size_t ev_index) { _evdev_devices[ev_index] = device; if (device->is_connected()) { - _connected_devices.add_device(MOVE(device)); + _connected_devices.add_device(std::move(device)); } else { // Wait for activity on the device before it is considered connected. - _inactive_devices.add_device(MOVE(device)); + _inactive_devices.add_device(std::move(device)); } return _evdev_devices[ev_index]; } @@ -189,10 +189,10 @@ consider_add_js_device(size_t js_index) { InputDevice *device_p = device.p(); if (device->is_connected()) { - _connected_devices.add_device(MOVE(device)); + _connected_devices.add_device(std::move(device)); } else { // Wait for activity on the device before it is considered connected. - _inactive_devices.add_device(MOVE(device)); + _inactive_devices.add_device(std::move(device)); } return device_p; } diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 6617fc1dcd..2c10ede51c 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -152,71 +152,71 @@ open_device() { ioctl(_fd, JSIOCGAXMAP, axmap); for (uint8_t i = 0; i < num_axes; ++i) { - ControlAxis axis = C_none; + Axis axis = Axis::none; switch (axmap[i]) { case ABS_X: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_x; + axis = InputDevice::Axis::left_x; } else if (_device_class == DC_flight_stick) { - axis = InputDevice::C_roll; + axis = InputDevice::Axis::roll; } else { - axis = InputDevice::C_x; + axis = InputDevice::Axis::x; } break; case ABS_Y: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_y; + axis = InputDevice::Axis::left_y; } else if (_device_class == DC_flight_stick) { - axis = InputDevice::C_pitch; + axis = InputDevice::Axis::pitch; } else { - axis = InputDevice::C_y; + axis = InputDevice::Axis::y; } break; case ABS_Z: if (_device_class == DC_gamepad) { - axis = C_left_trigger; + axis = Axis::left_trigger; } else { - //axis = C_trigger; + //axis = Axis::trigger; } break; case ABS_RX: - axis = C_right_x; + axis = Axis::right_x; break; case ABS_RY: - axis = C_right_y; + axis = Axis::right_y; break; case ABS_RZ: if (_device_class == DC_gamepad) { - axis = InputDevice::C_right_trigger; + axis = InputDevice::Axis::right_trigger; } else { - axis = InputDevice::C_yaw; + axis = InputDevice::Axis::yaw; } break; case ABS_THROTTLE: - axis = InputDevice::C_throttle; + axis = InputDevice::Axis::throttle; break; case ABS_RUDDER: - axis = InputDevice::C_rudder; + axis = InputDevice::Axis::rudder; break; case ABS_WHEEL: - axis = InputDevice::C_wheel; + axis = InputDevice::Axis::wheel; break; case ABS_GAS: - axis = InputDevice::C_accelerator; + axis = InputDevice::Axis::accelerator; break; case ABS_BRAKE: - axis = InputDevice::C_brake; + axis = InputDevice::Axis::brake; break; case ABS_HAT0X: @@ -231,7 +231,7 @@ open_device() { _buttons.push_back(ButtonState(GamepadButton::hat_left())); _buttons.push_back(ButtonState(GamepadButton::hat_right())); } - axis = C_none; + axis = Axis::none; } break; @@ -247,7 +247,7 @@ open_device() { _buttons.push_back(ButtonState(GamepadButton::hat_up())); _buttons.push_back(ButtonState(GamepadButton::hat_down())); } - axis = C_none; + axis = Axis::none; } break; @@ -256,17 +256,17 @@ open_device() { device_cat.debug() << "Unmapped /dev/input/js" << _index << " axis " << (int)i << ": 0x" << std::hex << (int)axmap[i] << "\n"; } - axis = C_none; + axis = Axis::none; break; } _controls[i].axis = axis; - if (axis == C_left_trigger || axis == C_right_trigger) { + if (axis == Axis::left_trigger || axis == Axis::right_trigger) { // We'd like to use 0.0 to indicate the resting position. _controls[i]._scale = 1.0 / 65534.0; _controls[i]._bias = 0.5; have_analog_triggers = true; - } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { + } else if (axis == Axis::left_y || axis == Axis::right_y || axis == Axis::y) { _controls[i]._scale = 1.0 / -32767.0; _controls[i]._bias = 0.0; } else { @@ -279,8 +279,8 @@ open_device() { if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) { // Emulate analog triggers. _ltrigger_control = (int)_controls.size(); - add_control(C_left_trigger, 0, 1, false); - add_control(C_right_trigger, 0, 1, false); + add_control(Axis::left_trigger, 0, 1, false); + add_control(Axis::right_trigger, 0, 1, false); } else { _ltrigger_button = -1; _rtrigger_button = -1; diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index f4603c5aa8..db8f82d76d 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -478,71 +478,71 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { is_signed = false; } - ControlAxis axis = C_none; + Axis axis = Axis::none; switch (cap.UsagePage) { case HID_USAGE_PAGE_GENERIC: switch (usage) { case HID_USAGE_GENERIC_X: if (_device_class == DC_gamepad) { - axis = C_left_x; + axis = Axis::left_x; } else if (_device_class == DC_flight_stick) { - axis = C_roll; + axis = Axis::roll; } else { - axis = C_x; + axis = Axis::x; } break; case HID_USAGE_GENERIC_Y: if (_device_class == DC_gamepad) { - axis = C_left_y; + axis = Axis::left_y; swap(cap.LogicalMin, cap.LogicalMax); } else if (_device_class == DC_flight_stick) { - axis = C_pitch; + axis = Axis::pitch; } else { - axis = C_y; + axis = Axis::y; swap(cap.LogicalMin, cap.LogicalMax); } break; case HID_USAGE_GENERIC_Z: if (_device_class == DC_gamepad) { - axis = C_left_trigger; + axis = Axis::left_trigger; } else if (_device_class == DC_flight_stick) { - axis = C_throttle; + axis = Axis::throttle; } else { - axis = C_z; + axis = Axis::z; swap(cap.LogicalMin, cap.LogicalMax); } break; case HID_USAGE_GENERIC_RX: if (_device_class == DC_gamepad) { - axis = C_right_x; + axis = Axis::right_x; } else { - axis = C_pitch; + axis = Axis::pitch; } break; case HID_USAGE_GENERIC_RY: if (_device_class == DC_gamepad) { - axis = C_right_y; + axis = Axis::right_y; } else { - axis = C_roll; + axis = Axis::roll; } swap(cap.LogicalMin, cap.LogicalMax); break; case HID_USAGE_GENERIC_RZ: if (_device_class == DC_gamepad) { - axis = C_right_trigger; + axis = Axis::right_trigger; } else { // Flip to match Panda's convention for heading. - axis = C_yaw; + axis = Axis::yaw; swap(cap.LogicalMin, cap.LogicalMax); } break; case HID_USAGE_GENERIC_SLIDER: // Flip to match Panda's convention for heading. - axis = C_rudder; + axis = Axis::rudder; swap(cap.LogicalMin, cap.LogicalMax); break; case HID_USAGE_GENERIC_WHEEL: - axis = C_wheel; + axis = Axis::wheel; break; case HID_USAGE_GENERIC_HATSWITCH: // This is handled specially. @@ -554,7 +554,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { } int control_index; - if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { + if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. control_index = add_control(axis, cap.LogicalMax, cap.LogicalMin, true); } else if (!is_signed) { diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index f2f0b45b85..7e1ddea132 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -315,42 +315,42 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { default: case XINPUT_DEVSUBTYPE_GAMEPAD: _device_class = DC_gamepad; - set_control_map(0, C_left_trigger); - set_control_map(1, C_right_trigger); - set_control_map(2, C_left_x); - set_control_map(3, C_left_y); - set_control_map(4, C_right_x); - set_control_map(5, C_right_y); + set_control_map(0, Axis::left_trigger); + set_control_map(1, Axis::right_trigger); + set_control_map(2, Axis::left_x); + set_control_map(3, Axis::left_y); + set_control_map(4, Axis::right_x); + set_control_map(5, Axis::right_y); break; case XINPUT_DEVSUBTYPE_WHEEL: _device_class = DC_steering_wheel; - set_control_map(0, C_brake); - set_control_map(1, C_accelerator); - set_control_map(2, C_wheel); - set_control_map(3, C_none); - set_control_map(4, C_none); - set_control_map(5, C_none); + set_control_map(0, Axis::brake); + set_control_map(1, Axis::accelerator); + set_control_map(2, Axis::wheel); + set_control_map(3, Axis::none); + set_control_map(4, Axis::none); + set_control_map(5, Axis::none); break; case XINPUT_DEVSUBTYPE_FLIGHT_STICK: _device_class = DC_flight_stick; - set_control_map(0, C_yaw); - set_control_map(1, C_throttle); - set_control_map(2, C_roll); - set_control_map(3, C_pitch); - set_control_map(4, C_none); - set_control_map(5, C_none); + set_control_map(0, Axis::yaw); + set_control_map(1, Axis::throttle); + set_control_map(2, Axis::roll); + set_control_map(3, Axis::pitch); + set_control_map(4, Axis::none); + set_control_map(5, Axis::none); break; case XINPUT_DEVSUBTYPE_DANCE_PAD: _device_class = DC_dance_pad; - set_control_map(0, C_none); - set_control_map(1, C_none); - set_control_map(2, C_none); - set_control_map(3, C_none); - set_control_map(4, C_none); - set_control_map(5, C_none); + set_control_map(0, Axis::none); + set_control_map(1, Axis::none); + set_control_map(2, Axis::none); + set_control_map(3, Axis::none); + set_control_map(4, Axis::none); + set_control_map(5, Axis::none); break; } From 8d8c5a546da1668740f01bd730799dd816ea7f44 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Sep 2018 14:50:00 +0200 Subject: [PATCH 63/82] input: rename "action_x" button handles to "face_x" --- panda/src/device/evdevInputDevice.cxx | 24 +++++++++--------- panda/src/device/ioKitInputDevice.cxx | 16 ++++++------ panda/src/device/linuxJoystickDevice.cxx | 2 +- panda/src/device/winRawInputDevice.cxx | 16 ++++++------ panda/src/device/xInputDevice.cxx | 9 +++---- panda/src/putil/gamepadButton.cxx | 32 ++++++++++++------------ panda/src/putil/gamepadButton.h | 16 ++++++------ 7 files changed, 57 insertions(+), 58 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index cf15b50f2a..72085f1ed9 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -906,10 +906,10 @@ map_button(int code, DeviceClass device_class) { if (device_class == DC_gamepad) { // Based on "Jess Tech Colour Rumble Pad" static const ButtonHandle mapping[] = { - GamepadButton::action_x(), - GamepadButton::action_y(), - GamepadButton::action_a(), - GamepadButton::action_b(), + GamepadButton::face_x(), + GamepadButton::face_y(), + GamepadButton::face_a(), + GamepadButton::face_b(), GamepadButton::lshoulder(), GamepadButton::ltrigger(), GamepadButton::rshoulder(), @@ -929,22 +929,22 @@ map_button(int code, DeviceClass device_class) { switch (code) { case BTN_A: - return GamepadButton::action_a(); + return GamepadButton::face_a(); case BTN_B: - return GamepadButton::action_b(); + return GamepadButton::face_b(); case BTN_C: - return GamepadButton::action_c(); + return GamepadButton::face_c(); case BTN_X: - return GamepadButton::action_x(); + return GamepadButton::face_x(); case BTN_Y: - return GamepadButton::action_y(); + return GamepadButton::face_y(); case BTN_Z: - return GamepadButton::action_z(); + return GamepadButton::face_z(); case BTN_TL: return GamepadButton::lshoulder(); @@ -959,10 +959,10 @@ map_button(int code, DeviceClass device_class) { return GamepadButton::rtrigger(); case BTN_1: - return GamepadButton::action_1(); + return GamepadButton::face_1(); case BTN_2: - return GamepadButton::action_2(); + return GamepadButton::face_2(); case BTN_SELECT: case KEY_PREVIOUS: diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index 3313e3f78a..0f0078404b 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -632,10 +632,10 @@ parse_element(IOHIDElementRef element) { // SNES-style USB gamepad static const ButtonHandle gamepad_buttons[] = { ButtonHandle::none(), - GamepadButton::action_x(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_y(), + GamepadButton::face_x(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_y(), GamepadButton::lshoulder(), GamepadButton::rshoulder(), ButtonHandle::none(), @@ -651,10 +651,10 @@ parse_element(IOHIDElementRef element) { // driver. I don't know if other drivers do the same thing at all. static const ButtonHandle gamepad_buttons[] = { ButtonHandle::none(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_x(), - GamepadButton::action_y(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_x(), + GamepadButton::face_y(), GamepadButton::lshoulder(), GamepadButton::rshoulder(), GamepadButton::lstick(), diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 2c10ede51c..e0f4d90348 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -134,7 +134,7 @@ open_device() { device_cat.debug() << "Unmapped /dev/input/js" << _index << " button " << (int)i << ": 0x" << std::hex << btnmap[i] << "\n"; } - } else if (handle == GamepadButton::action_a()) { + } else if (handle == GamepadButton::face_a()) { _device_class = DC_gamepad; } else if (handle == GamepadButton::trigger()) { _device_class = DC_flight_stick; diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index db8f82d76d..d61ad04d6e 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -332,10 +332,10 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // Do we have a button mapping? static const ButtonHandle gamepad_buttons_common[] = { ButtonHandle::none(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_x(), - GamepadButton::action_y(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_x(), + GamepadButton::face_y(), GamepadButton::lshoulder(), GamepadButton::rshoulder(), GamepadButton::start(), @@ -348,10 +348,10 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // SNES-style USB gamepad static const ButtonHandle gamepad_buttons_snes[] = { ButtonHandle::none(), - GamepadButton::action_x(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_y(), + GamepadButton::face_x(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_y(), GamepadButton::lshoulder(), GamepadButton::rshoulder(), ButtonHandle::none(), diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 7e1ddea132..680bbd26dd 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -386,11 +386,10 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { set_button_map(8, GamepadButton::lshoulder()); set_button_map(9, GamepadButton::rshoulder()); set_button_map(10, GamepadButton::guide()); - set_button_map(11, GamepadButton::action_a()); - set_button_map(12, GamepadButton::action_b()); - set_button_map(13, GamepadButton::action_x()); - set_button_map(14, GamepadButton::action_y()); - + set_button_map(11, GamepadButton::face_a()); + set_button_map(12, GamepadButton::face_b()); + set_button_map(13, GamepadButton::face_x()); + set_button_map(14, GamepadButton::face_y()); if (caps.Vibration.wLeftMotorSpeed != 0 || caps.Vibration.wRightMotorSpeed != 0) { diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index 12fae57b50..2924f0bf90 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -37,15 +37,15 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(start) DEFINE_GAMEPAD_BUTTON_HANDLE(next) DEFINE_GAMEPAD_BUTTON_HANDLE(previous) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_a) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_b) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_c) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_x) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_y) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_z) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_a) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_b) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_c) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_x) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_y) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_z) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_1) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_2) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_1) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_2) DEFINE_GAMEPAD_BUTTON_HANDLE(trigger) DEFINE_GAMEPAD_BUTTON_HANDLE(hat_up) @@ -100,15 +100,15 @@ init_gamepad_buttons() { ButtonRegistry::ptr()->register_button(_next, "next"); ButtonRegistry::ptr()->register_button(_previous, "previous"); - ButtonRegistry::ptr()->register_button(_action_a, "action_a"); - ButtonRegistry::ptr()->register_button(_action_b, "action_b"); - ButtonRegistry::ptr()->register_button(_action_c, "action_c"); - ButtonRegistry::ptr()->register_button(_action_x, "action_x"); - ButtonRegistry::ptr()->register_button(_action_y, "action_y"); - ButtonRegistry::ptr()->register_button(_action_z, "action_z"); + ButtonRegistry::ptr()->register_button(_face_a, "face_a"); + ButtonRegistry::ptr()->register_button(_face_b, "face_b"); + ButtonRegistry::ptr()->register_button(_face_c, "face_c"); + ButtonRegistry::ptr()->register_button(_face_x, "face_x"); + ButtonRegistry::ptr()->register_button(_face_y, "face_y"); + ButtonRegistry::ptr()->register_button(_face_z, "face_z"); - ButtonRegistry::ptr()->register_button(_action_1, "action_1"); - ButtonRegistry::ptr()->register_button(_action_2, "action_2"); + ButtonRegistry::ptr()->register_button(_face_1, "face_1"); + ButtonRegistry::ptr()->register_button(_face_2, "face_2"); ButtonRegistry::ptr()->register_button(_trigger, "trigger"); ButtonRegistry::ptr()->register_button(_hat_up, "hat_up"); diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index 768f634380..774ee8ef2f 100644 --- a/panda/src/putil/gamepadButton.h +++ b/panda/src/putil/gamepadButton.h @@ -43,15 +43,15 @@ PUBLISHED: static ButtonHandle next(); static ButtonHandle previous(); - static ButtonHandle action_a(); - static ButtonHandle action_b(); - static ButtonHandle action_c(); - static ButtonHandle action_x(); - static ButtonHandle action_y(); - static ButtonHandle action_z(); + static ButtonHandle face_a(); + static ButtonHandle face_b(); + static ButtonHandle face_c(); + static ButtonHandle face_x(); + static ButtonHandle face_y(); + static ButtonHandle face_z(); - static ButtonHandle action_1(); - static ButtonHandle action_2(); + static ButtonHandle face_1(); + static ButtonHandle face_2(); // Flight stick buttons, takes zero-based index. First is always trigger. static ButtonHandle trigger(); From 8edc01930774e3190144a9433ff45e5ba4160ae5 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Sep 2018 15:12:17 +0200 Subject: [PATCH 64/82] device: overhaul InputDevice API: - Rename "controls" terminology for analog inputs to "axes" - Change some naming of accessors - Unexpose methods where they are available as properties - Add serial_number field - Put battery levels under a "battery" field --- panda/src/device/analogNode.I | 6 +- panda/src/device/analogNode.cxx | 16 +- panda/src/device/buttonNode.I | 4 +- panda/src/device/clientAnalogDevice.cxx | 2 +- panda/src/device/evdevInputDevice.cxx | 30 ++-- panda/src/device/evdevInputDevice.h | 4 +- panda/src/device/inputDevice.I | 185 ++++++++++------------ panda/src/device/inputDevice.cxx | 131 ++++++++-------- panda/src/device/inputDevice.h | 187 ++++++++++++----------- panda/src/device/inputDeviceManager.h | 3 - panda/src/device/inputDeviceNode.cxx | 18 +++ panda/src/device/inputDeviceNode.h | 14 +- panda/src/device/ioKitInputDevice.cxx | 8 +- panda/src/device/linuxJoystickDevice.cxx | 48 +++--- panda/src/device/winRawInputDevice.cxx | 20 +-- panda/src/device/winRawInputDevice.h | 12 +- panda/src/device/xInputDevice.cxx | 143 ++++++++--------- panda/src/vrpn/vrpnAnalog.cxx | 8 +- panda/src/vrpn/vrpnTracker.cxx | 24 +-- 19 files changed, 439 insertions(+), 424 deletions(-) diff --git a/panda/src/device/analogNode.I b/panda/src/device/analogNode.I index 5fd3543c07..a61ff00b43 100644 --- a/panda/src/device/analogNode.I +++ b/panda/src/device/analogNode.I @@ -35,7 +35,7 @@ is_valid() const { */ INLINE int AnalogNode:: get_num_controls() const { - return _analog->get_num_controls(); + return _analog->get_num_axes(); } /** @@ -45,7 +45,7 @@ get_num_controls() const { */ INLINE double AnalogNode:: get_control_state(int index) const { - return _analog->get_control_state(index); + return _analog->get_axis_value(index); } /** @@ -54,7 +54,7 @@ get_control_state(int index) const { */ INLINE bool AnalogNode:: is_control_known(int index) const { - return _analog->is_control_known(index); + return _analog->is_axis_known(index); } /** diff --git a/panda/src/device/analogNode.cxx b/panda/src/device/analogNode.cxx index fffdadd579..6f9b42811c 100644 --- a/panda/src/device/analogNode.cxx +++ b/panda/src/device/analogNode.cxx @@ -81,7 +81,7 @@ write(std::ostream &out, int indent_level) const { DataNode::write(out, indent_level); if (_analog != nullptr) { - _analog->write_controls(out, indent_level + 2); + _analog->write_axes(out, indent_level + 2); } } @@ -101,12 +101,14 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, LPoint2 out(0.0f, 0.0f); for (int i = 0; i < max_outputs; i++) { - if (_outputs[i]._index >= 0 && - _analog->is_control_known(_outputs[i]._index)) { - if (_outputs[i]._flip) { - out[i] = -_analog->get_control_state(_outputs[i]._index); - } else { - out[i] = _analog->get_control_state(_outputs[i]._index); + if (_outputs[i]._index >= 0) { + InputDevice::AxisState state = _analog->get_axis(_outputs[i]._index); + if (state.known) { + if (_outputs[i]._flip) { + out[i] = -state.value; + } else { + out[i] = state.value; + } } } } diff --git a/panda/src/device/buttonNode.I b/panda/src/device/buttonNode.I index 1debd87ff7..013f66e41d 100644 --- a/panda/src/device/buttonNode.I +++ b/panda/src/device/buttonNode.I @@ -43,7 +43,7 @@ get_num_buttons() const { */ INLINE void ButtonNode:: set_button_map(int index, ButtonHandle button) { - _device->set_button_map(index, button); + _device->map_button(index, button); } /** @@ -62,7 +62,7 @@ get_button_map(int index) const { */ INLINE bool ButtonNode:: get_button_state(int index) const { - return _device->get_button_state(index); + return _device->is_button_pressed(index); } /** diff --git a/panda/src/device/clientAnalogDevice.cxx b/panda/src/device/clientAnalogDevice.cxx index 71fadfeb99..f9eef0f884 100644 --- a/panda/src/device/clientAnalogDevice.cxx +++ b/panda/src/device/clientAnalogDevice.cxx @@ -23,5 +23,5 @@ TypeHandle ClientAnalogDevice::_type_handle; void ClientAnalogDevice:: write(std::ostream &out, int indent_level) const { indent(out, indent_level) << get_type() << " " << get_name() << ":\n"; - write_controls(out, indent_level + 2); + write_axes(out, indent_level + 2); } diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 72085f1ed9..4fdd451cd5 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -392,10 +392,10 @@ init_device() { } if (test_bit(i, states)) { - button.state = S_down; + button._state = S_down; all_values_zero = false; } else { - button.state = S_up; + button._state = S_up; } if (button.handle == GamepadButton::dpad_left()) { emulate_dpad = false; @@ -415,7 +415,7 @@ init_device() { } if (has_axes) { - _control_indices.resize(num_bits, -1); + _axis_indices.resize(num_bits, -1); for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { @@ -545,12 +545,12 @@ init_device() { std::swap(absinfo.maximum, absinfo.minimum); } if (axis == Axis::throttle && (quirks & QB_centered_throttle) != 0) { - index = add_control(axis, absinfo.maximum, absinfo.minimum, true); + index = add_axis(axis, absinfo.maximum, absinfo.minimum, true); } else { - index = add_control(axis, absinfo.minimum, absinfo.maximum); + index = add_axis(axis, absinfo.minimum, absinfo.maximum); } - control_changed(index, absinfo.value); - _control_indices[i] = index; + axis_changed(index, absinfo.value); + _axis_indices[i] = index; if (absinfo.value != 0) { all_values_zero = false; @@ -583,9 +583,9 @@ init_device() { if (_ltrigger_code >= 0 && _rtrigger_code >= 0 && !have_analog_triggers) { // Emulate analog triggers. - _ltrigger_control = (int)_controls.size(); - add_control(Axis::left_trigger, 0, 1, false); - add_control(Axis::right_trigger, 0, 1, false); + _ltrigger_axis = (int)_axes.size(); + add_axis(Axis::left_trigger, 0, 1, false); + add_axis(Axis::right_trigger, 0, 1, false); } else { _ltrigger_code = -1; _rtrigger_code = -1; @@ -709,10 +709,10 @@ process_events() { button_changed(_dpad_up_button, events[i].value < 0); button_changed(_dpad_up_button+1, events[i].value > 0); } - nassertd(code >= 0 && (size_t)code < _control_indices.size()) break; - index = _control_indices[code]; + nassertd(code >= 0 && (size_t)code < _axis_indices.size()) break; + index = _axis_indices[code]; if (index >= 0) { - control_changed(index, events[i].value); + axis_changed(index, events[i].value); } break; @@ -723,9 +723,9 @@ process_events() { button_changed(index, events[i].value != 0); } if (code == _ltrigger_code) { - control_changed(_ltrigger_control, events[i].value); + axis_changed(_ltrigger_axis, events[i].value); } else if (code == _rtrigger_code) { - control_changed(_ltrigger_control + 1, events[i].value); + axis_changed(_ltrigger_axis + 1, events[i].value); } break; diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index d82ba5f980..239ba78616 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -49,7 +49,7 @@ private: int _ff_strong; int _ff_weak; - pvector _control_indices; + pvector _axis_indices; pvector _button_indices; // These are used for D-pad emulation. @@ -59,7 +59,7 @@ private: int _dpad_up_button; // This is used for axis emulation. - int _ltrigger_control; + int _ltrigger_axis; int _ltrigger_code; int _rtrigger_code; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index a8eb5a127b..bcddc5cf52 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -24,9 +24,7 @@ InputDevice() : _product_id(0), _is_connected(false), _event_sequence(0), - _enable_pointer_events(false), - _battery_level(-1), - _max_battery_level(-1) + _enable_pointer_events(false) { _button_events = new ButtonEventList; } @@ -50,6 +48,16 @@ get_manufacturer() const { return _manufacturer; } +/** + * Returns a string containing the serial number of the device, if this + * information is known. + */ +INLINE std::string InputDevice:: +get_serial_number() const { + LightMutexHolder holder(_lock); + return _serial_number; +} + /** * Returns a string containing the USB vendor ID of the device, if this * information is known. @@ -160,23 +168,12 @@ get_tracker() const { /** * Returns a rough indication of the battery level, ranging from 0 (completely - * empty battery) to the number reported by get_max_battery_level(), which - * represents a full battery. If this information is not known, returns -1. + * empty battery) to the indicated max_level value. */ -INLINE short InputDevice:: -get_battery_level() const { +INLINE InputDevice::BatteryData InputDevice:: +get_battery() const { LightMutexHolder holder(_lock); - return _battery_level; -} - -/** - * Returns the maximum value that may be reported by get_battery_level(), - * representing a full battery. Returns -1 if no battery information is known. - */ -INLINE short InputDevice:: -get_max_battery_level() const { - LightMutexHolder holder(_lock); - return _max_battery_level; + return _battery_data; } /** @@ -202,7 +199,7 @@ get_num_buttons() const { * generate ButtonEvents when the buttons change state. */ INLINE void InputDevice:: -set_button_map(size_t index, ButtonHandle button) { +map_button(size_t index, ButtonHandle button) { LightMutexHolder holder(_lock); if (index >= _buttons.size()) { _buttons.resize(index + 1, ButtonState()); @@ -213,7 +210,7 @@ set_button_map(size_t index, ButtonHandle button) { /** * Returns the ButtonHandle that was previously associated with the given index - * number by a call to set_button_map(), or ButtonHandle::none() if no button + * number by a call to map_button(), or ButtonHandle::none() if no button * was associated. */ INLINE ButtonHandle InputDevice:: @@ -230,9 +227,9 @@ get_button_map(size_t index) const { * currently known to be down, or false if it is up or unknown. */ INLINE bool InputDevice:: -get_button_state(size_t index) const { +is_button_pressed(size_t index) const { if (index < _buttons.size()) { - return (_buttons[index].state == S_down); + return (_buttons[index]._state == S_down); } else { return false; } @@ -245,14 +242,14 @@ get_button_state(size_t index) const { INLINE bool InputDevice:: is_button_known(size_t index) const { if (index < _buttons.size()) { - return _buttons[index].state != S_unknown; + return _buttons[index]._state != S_unknown; } else { return false; } } /** - * Returns the ButtonState that is set at the given index, or throw an assection + * Returns the ButtonState that is set at the given index, or throw an assert * if the index was not found in the list. */ INLINE InputDevice::ButtonState InputDevice:: @@ -261,13 +258,13 @@ get_button(size_t index) const { return _buttons[index]; } else { device_cat.error() - << "Index " << index << " was not found in the controls list\n"; + << "Index " << index << " was not found in the axes list\n"; return ButtonState(); } } /** - * Returns the first ButtonState found with the given axis, or throw an assection + * Returns the first ButtonState found with the given axis, or throw an assert * if the button handle was not found in the list. */ INLINE InputDevice::ButtonState InputDevice:: @@ -278,103 +275,89 @@ find_button(ButtonHandle handle) const { } } device_cat.error() - << "Handle " << handle.get_name() << " was not found in the controls list\n"; + << "Handle " << handle.get_name() << " was not found in the axes list\n"; return ButtonState(); } /** - * Returns the number of analog controls known to the InputDevice. This number - * may change as more controls are discovered. + * Returns the number of analog axes known to the InputDevice. This number + * may change as more axes are discovered. */ INLINE size_t InputDevice:: -get_num_controls() const { - return _controls.size(); +get_num_axes() const { + return _axes.size(); } /** - * Associates the indicated Axis with the control of the indicated index + * Associates the indicated Axis with the axis of the indicated index * number. Pass Axis::none to turn off any association. * * It is not necessary to call this if you simply want to query the state of - * the various controls by index number. + * the various axes by index number. */ INLINE void InputDevice:: -set_control_map(size_t index, InputDevice::Axis axis) { +map_axis(size_t index, InputDevice::Axis axis) { LightMutexHolder holder(_lock); - if (index >= _controls.size()) { - _controls.resize(index + 1, AnalogState()); + if (index >= _axes.size()) { + _axes.resize(index + 1, AxisState()); } - _controls[index].axis = axis; + _axes[index].axis = axis; } /** - * Returns the Axis that was previously associated with the given index - * number by a call to set_control_map(), or Axis::none if no control was - * associated. - */ -INLINE InputDevice::Axis InputDevice:: -get_control_map(size_t index) const { - if (index < _controls.size()) { - return _controls[index].axis; - } else { - return Axis::none; - } -} - -/** - * Returns the current position of indicated analog control (identified by its - * index number), or 0.0 if the control is unknown. The normal range of a - * single control is -1.0 to 1.0. + * Returns the current position of indicated analog axis (identified by its + * index number), or 0.0 if the axis is unknown. The normal range of a + * single axis is -1.0 to 1.0. */ INLINE double InputDevice:: -get_control_state(size_t index) const { - if (index < _controls.size()) { - return _controls[index].state; +get_axis_value(size_t index) const { + if (index < _axes.size()) { + return _axes[index].value; } else { return 0.0; } } /** - * Returns the AnalogAxis that is set at the given index, or throw an assection + * Returns the axis state that is set at the given index, or throw an assert * if the index was not found in the list. */ -INLINE InputDevice::AnalogState InputDevice:: -get_control(size_t index) const { - if (index < _controls.size()) { - return _controls[index]; +INLINE InputDevice::AxisState InputDevice:: +get_axis(size_t index) const { + if (index < _axes.size()) { + return _axes[index]; } else { device_cat.error() - << "Index " << index<< " was not found in the controls list\n"; - return AnalogState(); + << "Index " << index << " was not found in the axes list\n"; + return AxisState(); } } /** - * Returns the first AnalogAxis found with the given axis, or throw an assection + * Returns the first AnalogAxis found with the given axis, or throw an assert * if the axis was not found in the list. */ -INLINE InputDevice::AnalogState InputDevice:: -find_control(InputDevice::Axis axis) const { - for (size_t i = 0; i < _controls.size(); ++i) { - if (_controls[i].axis == axis) { - return _controls[i]; +INLINE InputDevice::AxisState InputDevice:: +find_axis(InputDevice::Axis axis) const { + for (size_t i = 0; i < _axes.size(); ++i) { + if (_axes[i].axis == axis) { + return _axes[i]; } } device_cat.error() - << "Axis " << axis << " was not found in the controls list\n"; - return AnalogState(); + << "Axis " << axis << " was not found in the axes list\n"; + return AxisState(); } /** - * Returns true if the state of the indicated analog control is known, or false - * if we have never heard anything about this particular control. + * Returns true if the state of the indicated analog axis is known, or false + * if we have never heard anything about this particular axis. */ INLINE bool InputDevice:: -is_control_known(size_t index) const { - if (index < _controls.size()) { - return _controls[index].known; +is_axis_known(size_t index) const { + if (index < _axes.size()) { + return _axes[index].known; } else { return false; } @@ -382,8 +365,8 @@ is_control_known(size_t index) const { /** * Sets the strength of the vibration effect, if supported. The values are - * clamped to 0-1 range. The first value controls the low-frequency rumble - * motor, whereas the second controls the high-frequency motor, if present. + * clamped to 0-1 range. The first value axes the low-frequency rumble + * motor, whereas the second axes the high-frequency motor, if present. */ INLINE void InputDevice:: set_vibration(double strong, double weak) { @@ -420,36 +403,26 @@ set_connected(bool connected) { _is_connected = connected; } -/** - * - */ -INLINE bool InputDevice:: -operator == (const InputDevice &) const { - return true; -} - -/** - * - */ -INLINE bool InputDevice:: -operator != (const InputDevice &) const { - return false; -} - -/** - * - */ -INLINE bool InputDevice:: -operator < (const InputDevice &) const { - return false; -} - /** * */ INLINE InputDevice::ButtonState:: ButtonState(ButtonHandle handle) : - handle(handle), - state(S_unknown) -{ + handle(handle) { +} + +/** + * True if the button state is currently known. + */ +ALWAYS_INLINE bool InputDevice::ButtonState:: +is_known() const { + return (_state != S_unknown); +} + +/** + * True if the button is currently known to be pressed. + */ +ALWAYS_INLINE bool InputDevice::ButtonState:: +is_pressed() const { + return (_state == S_down); } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index e24cd9a0df..aedfeb8222 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -32,8 +32,6 @@ InputDevice(const std::string &name, DeviceClass dev_class, int flags) : _is_connected(true), _event_sequence(0), _enable_pointer_events(false), - _battery_level(-1), - _max_battery_level(-1), _lock("InputDevice") { _button_events = new ButtonEventList; @@ -119,13 +117,22 @@ get_pointer_events() { return result; } - /** - * Called by the implementation to add a new known control. + * Called by the implementation to add a new known button. */ int InputDevice:: -add_control(Axis axis, int minimum, int maximum, bool centered) { - AnalogState state; +add_button(ButtonHandle button) { + int index = (int)_buttons.size(); + _buttons.push_back(ButtonState(button)); + return index; +} + +/** + * Called by the implementation to add a new known axis. + */ +int InputDevice:: +add_axis(Axis axis, int minimum, int maximum, bool centered) { + AxisState state; state.axis = axis; if (centered) { // Centered, eg. for sticks. @@ -136,17 +143,17 @@ add_control(Axis axis, int minimum, int maximum, bool centered) { state._scale = 1.0 / maximum; state._bias = 0.0; } - int index = (int)_controls.size(); - _controls.push_back(state); + int index = (int)_axes.size(); + _axes.push_back(state); return index; } /** - * Called by the implementation to add a new known control. This version - * tries to guess whether the control is centered or not. + * Called by the implementation to add a new known axis. This version tries + * to guess whether the axis is centered or not. */ int InputDevice:: -add_control(Axis axis, int minimum, int maximum) { +add_axis(Axis axis, int minimum, int maximum) { bool centered = (minimum < 0) || axis == Axis::x || axis == Axis::y @@ -160,7 +167,7 @@ add_control(Axis axis, int minimum, int maximum) { || axis == Axis::right_y || axis == Axis::wheel || axis == Axis::rudder; - return add_control(axis, minimum, maximum, centered); + return add_axis(axis, minimum, maximum, centered); } /** @@ -246,10 +253,10 @@ button_changed(int index, bool down) { } State new_state = down ? S_down : S_up; - if (_buttons[index].state == new_state) { + if (_buttons[index]._state == new_state) { return; } - _buttons[index].state = new_state; + _buttons[index]._state = new_state; ButtonHandle handle = _buttons[index].handle; @@ -272,65 +279,67 @@ button_changed(int index, bool down) { /** * Sets the state of the indicated analog index. The caller should ensure that * the lock is held while this call is made. This should be a number in the - * range -1.0 to 1.0, representing the current position of the control within - * its total range of movement. + * range -1.0 to 1.0, representing the current position of the axis within its + * total range of movement. */ void InputDevice:: -set_control_state(int index, double state) { - nassertv(_lock.debug_is_locked()); +set_axis_value(int index, double value) { + LightMutexHolder holder(_lock); + nassertv(index >= 0); - if (index >= (int)_controls.size()) { - _controls.resize(index + 1, AnalogState()); + if ((size_t)index >= _axes.size()) { + _axes.resize((size_t)index + 1u, AxisState()); } - if (device_cat.is_spam() && _controls[index].state != state) { + if (device_cat.is_spam() && _axes[index].value != value) { device_cat.spam() - << "Changed control " << index; + << "Changed axis " << index; - if (_controls[index].axis != Axis::none) { - device_cat.spam(false) << " (" << _controls[index].axis << ")"; + if (_axes[index].axis != Axis::none) { + device_cat.spam(false) << " (" << _axes[index].axis << ")"; } - device_cat.spam(false) << " to " << state << "\n"; + device_cat.spam(false) << " to " << value << "\n"; } - _controls[index].state = state; - _controls[index].known = true; + _axes[index].value = value; + _axes[index].known = true; } /** - * Like set_control_state, but instead passes a raw, unscaled value. + * Called by the implementation during do_poll to indicate that the indicated + * axis has received a new raw value. Assumes the lock is held. */ void InputDevice:: -control_changed(int index, int state) { +axis_changed(int index, int state) { nassertv(_lock.debug_is_locked()); nassertv(index >= 0); - if (index >= (int)_controls.size()) { - _controls.resize(index + 1, AnalogState()); + if ((size_t)index >= _axes.size()) { + _axes.resize((size_t)index + 1u, AxisState()); } - double new_state = fma((double)state, _controls[index]._scale, _controls[index]._bias); + double value = fma((double)state, _axes[index]._scale, _axes[index]._bias); - if (device_cat.is_spam() && _controls[index].state != new_state) { + if (device_cat.is_spam() && !IS_NEARLY_EQUAL(_axes[index].value, value)) { device_cat.spam() - << "Changed control " << index; + << "Changed axis " << index; - if (_controls[index].axis != Axis::none) { - device_cat.spam(false) << " (" << _controls[index].axis << ")"; + if (_axes[index].axis != Axis::none) { + device_cat.spam(false) << " (" << _axes[index].axis << ")"; } - device_cat.spam(false) << " to " << new_state << " (raw value " << state << ")\n"; + device_cat.spam(false) << " to " << value << " (raw value " << state << ")\n"; } - _controls[index].state = new_state; - _controls[index].known = true; + _axes[index].value = value; + _axes[index].known = true; } /** * Records that a tracker movement has taken place. */ void InputDevice:: -set_tracker(const LPoint3 &pos, const LOrientation &orient, double time) { +tracker_changed(const LPoint3 &pos, const LOrientation &orient, double time) { nassertv(_lock.debug_is_locked()); _tracker_data.set_pos(pos); @@ -364,11 +373,9 @@ output(std::ostream &out) const { } } - if (_controls.size() > 0) { - out << ", " << _controls.size() << " control"; - if (_controls.size() != 1) { - out.put('s'); - } + if (_axes.size() > 0) { + out << ", " << _axes.size() << " ax" + << (_axes.size() != 1 ? 'e' : 'i') << 's'; } if (_flags & IDF_has_pointer) { @@ -386,13 +393,13 @@ output(std::ostream &out) const { if (_flags & IDF_has_battery) { out << ", battery"; - if (_battery_level > 0 && _max_battery_level > 0) { + if (_battery_data.level > 0 && _battery_data.max_level > 0) { out << " ["; short i = 0; - for (; i < _battery_level; ++i) { + for (; i < _battery_data.level; ++i) { out << '='; } - for (; i < _max_battery_level; ++i) { + for (; i < _battery_data.max_level; ++i) { out << ' '; } out << ']'; @@ -411,13 +418,13 @@ output_buttons(std::ostream &out) const { Buttons::const_iterator bi; for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { const ButtonState &state = (*bi); - if (state.state != S_unknown) { + if (state.is_known()) { if (any_buttons) { out << ", "; } any_buttons = true; out << (int)(bi - _buttons.begin()) << "="; - if (state.state == S_up) { + if (state._state == S_up) { out << "up"; } else { out << "down"; @@ -439,7 +446,7 @@ write_buttons(std::ostream &out, int indent_level) const { Buttons::const_iterator bi; for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { const ButtonState &state = (*bi); - if (state.state != S_unknown) { + if (state.is_known()) { any_buttons = true; indent(out, indent_level) @@ -449,7 +456,7 @@ write_buttons(std::ostream &out, int indent_level) const { out << "(" << state.handle << ") "; } - if (state.state == S_up) { + if (state._state == S_up) { out << "up"; } else { out << "down"; @@ -465,27 +472,27 @@ write_buttons(std::ostream &out, int indent_level) const { } /** - * Writes a multi-line description of the current analog control states. + * Writes a multi-line description of the current analog axis states. */ void InputDevice:: -write_controls(std::ostream &out, int indent_level) const { +write_axes(std::ostream &out, int indent_level) const { LightMutexHolder holder(_lock); - bool any_controls = false; - Controls::const_iterator ai; - for (ai = _controls.begin(); ai != _controls.end(); ++ai) { - const AnalogState &state = (*ai); + bool any_axis = false; + Axes::const_iterator ai; + for (ai = _axes.begin(); ai != _axes.end(); ++ai) { + const AxisState &state = (*ai); if (state.known) { - any_controls = true; + any_axis = true; indent(out, indent_level) - << (int)(ai - _controls.begin()) << ". " << state.state << "\n"; + << (int)(ai - _axes.begin()) << ". " << state.value << "\n"; } } - if (!any_controls) { + if (!any_axis) { indent(out, indent_level) - << "(no known analog controls)\n"; + << "(no known analog axes)\n"; } } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 77b42b43c0..7e4d4f623b 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -33,12 +33,12 @@ typedef MouseData PointerData; /** * This is a structure representing a single input device. Input devices may - * have zero or more buttons, pointers, or controls associated with them, and + * have zero or more buttons, pointers, or axes associated with them, and * optionally a motion tracker. * * These devices are brought under a common interface because there is such a * large range of devices out there that may support any number of these types - * of controls, we couldn't even begin to cover them with type-specific + * of axes, we couldn't even begin to cover them with type-specific * subclasses. * * Use the various has_() and get_num_() methods to determine information about @@ -85,16 +85,6 @@ PUBLISHED: DC_COUNT, }; -protected: - InputDevice(const std::string &name, DeviceClass dev_class, int flags); - -public: - InputDevice(); - InputDevice(const InputDevice ©); - void operator = (const InputDevice ©); - ~InputDevice(); - -PUBLISHED: enum class Axis { none, @@ -126,19 +116,94 @@ PUBLISHED: brake, }; + enum State { + S_unknown, + S_up, + S_down + }; + + class ButtonState { + public: + constexpr ButtonState() = default; + INLINE ButtonState(ButtonHandle handle); + ALWAYS_INLINE bool is_known() const; + ALWAYS_INLINE bool is_pressed() const; + + PUBLISHED: + MAKE_PROPERTY(known, is_known); + MAKE_PROPERTY(pressed, is_pressed); + + ButtonHandle handle = ButtonHandle::none(); + + public: + State _state = S_unknown; + }; + + class AxisState { + public: + constexpr AxisState() = default; + + PUBLISHED: + Axis axis = Axis::none; + double value = 0.0; + bool known = false; + + public: + double _scale = 1.0; + double _bias = 0.0; + }; + + class BatteryData { + PUBLISHED: + // Ranges from 0 through max_level. + short level = -1; + + // Maximum value of 'level' field. + short max_level = -1; + }; + +protected: + InputDevice(const std::string &name, DeviceClass dev_class, int flags); + +public: + InputDevice(); + InputDevice(const InputDevice ©); + void operator = (const InputDevice ©); + ~InputDevice(); + INLINE std::string get_name() const; INLINE std::string get_manufacturer() const; + INLINE std::string get_serial_number() const; INLINE unsigned short get_vendor_id() const; INLINE unsigned short get_product_id() const; INLINE bool is_connected() const; INLINE DeviceClass get_device_class() const; + INLINE PointerData get_pointer() const; + INLINE TrackerData get_tracker() const; + INLINE BatteryData get_battery() const; + + INLINE size_t get_num_buttons() const; + INLINE ButtonHandle get_button_map(size_t index) const; + INLINE bool is_button_pressed(size_t index) const; + INLINE bool is_button_known(size_t index) const; + INLINE ButtonState get_button(size_t index) const; + + INLINE size_t get_num_axes() const; + INLINE double get_axis_value(size_t index) const; + INLINE bool is_axis_known(size_t index) const; + INLINE AxisState get_axis(size_t index) const; + +PUBLISHED: // The human-readable name of this input device. MAKE_PROPERTY(name, get_name); // The device's manufacturer, or the empty string if not known. MAKE_PROPERTY(manufacturer, get_manufacturer); + // The device's serial number, or the empty string if not known. + MAKE_PROPERTY(serial_number, get_serial_number); + // USB vendor ID of the device, or 0 if not known. MAKE_PROPERTY(vendor_id, get_vendor_id); @@ -159,24 +224,22 @@ PUBLISHED: INLINE bool has_vibration() const; INLINE bool has_battery() const; - INLINE PointerData get_pointer() const; - INLINE TrackerData get_tracker() const; + // Getters for the various types of device data. + MAKE_PROPERTY2(pointer, has_pointer, get_pointer); + MAKE_PROPERTY2(tracker, has_tracker, get_tracker); + MAKE_PROPERTY2(battery, has_battery, get_battery); - INLINE short get_battery_level() const; - INLINE short get_max_battery_level() const; + // Make device buttons and axes iterable + MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button); + MAKE_SEQ_PROPERTY(axes, get_num_axes, get_axis); - INLINE size_t get_num_buttons() const; - INLINE void set_button_map(size_t index, ButtonHandle button); - INLINE ButtonHandle get_button_map(size_t index) const; - INLINE bool get_button_state(size_t index) const; - INLINE bool is_button_known(size_t index) const; - - INLINE size_t get_num_controls() const; - INLINE void set_control_map(size_t index, Axis axis); - INLINE Axis get_control_map(size_t index) const; - INLINE double get_control_state(size_t index) const; - INLINE bool is_control_known(size_t index) const; + // Associate buttons/axes with symbolic handles. + INLINE void map_button(size_t index, ButtonHandle handle); + INLINE void map_axis(size_t index, Axis axis); + INLINE ButtonState find_button(ButtonHandle handle) const; + INLINE AxisState find_axis(Axis axis) const; + // Enable rumble force-feedback effects INLINE void set_vibration(double strong, double weak); INLINE void enable_pointer_events(); @@ -194,19 +257,20 @@ PUBLISHED: static std::string format_axis(Axis axis); protected: - // Called during the constructor to add new controls or buttons - int add_control(Axis axis, int minimum, int maximum, bool centered); - int add_control(Axis axis, int minimum, int maximum); + // Called during the constructor to add new axes or buttons + int add_button(ButtonHandle handle); + int add_axis(Axis axis, int minimum, int maximum, bool centered); + int add_axis(Axis axis, int minimum, int maximum); void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); void pointer_moved(double x, double y, double time); void button_changed(int index, bool down); - void control_changed(int index, int value); - void set_control_state(int index, double state); + void axis_changed(int index, int value); + void set_axis_value(int index, double state); - void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); + void tracker_changed(const LPoint3 &pos, const LOrientation &orient, double time); virtual void do_set_vibration(double low, double high); virtual void do_poll(); @@ -214,16 +278,9 @@ protected: public: INLINE void set_connected(bool connected); - // We need these methods to make VC++ happy when we try to - // instantiate a pvector. They don't do - // anything useful. - INLINE bool operator == (const InputDevice &other) const; - INLINE bool operator != (const InputDevice &other) const; - INLINE bool operator < (const InputDevice &other) const; - void output_buttons(std::ostream &out) const; void write_buttons(std::ostream &out, int indent_level) const; - void write_controls(std::ostream &out, int indent_level) const; + void write_axes(std::ostream &out, int indent_level) const; protected: enum InputDeviceFlags { @@ -263,56 +320,14 @@ protected: PT(PointerEventList) _pointer_events; PUBLISHED: - enum State { - S_unknown, - S_up, - S_down - }; - - class ButtonState { - public: - constexpr ButtonState() = default; - INLINE ButtonState(ButtonHandle handle); - - PUBLISHED: - ButtonHandle handle = ButtonHandle::none(); - State state = S_unknown; - }; typedef pvector Buttons; + typedef pvector Axes; Buttons _buttons; + Axes _axes; - class AnalogState { - public: - constexpr AnalogState() = default; - - PUBLISHED: - Axis axis = Axis::none; - double state = 0.0; - bool known = false; - - public: - double _scale = 1.0; - double _bias = 0.0; - }; - typedef pvector Controls; - Controls _controls; - - short _battery_level; - short _max_battery_level; - + BatteryData _battery_data; TrackerData _tracker_data; - - INLINE ButtonState get_button(size_t index) const; - INLINE ButtonState find_button(ButtonHandle handle) const; - - INLINE AnalogState get_control(size_t index) const; - INLINE AnalogState find_control(Axis axis) const; - - // Make device buttons and controls iterable - MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button); - MAKE_SEQ_PROPERTY(controls, get_num_controls, get_control); - public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index e231032a34..9cfadf2845 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -46,9 +46,6 @@ PUBLISHED: INLINE static InputDeviceManager *get_global_ptr(); - // The set of all currently connected devices. - MAKE_PROPERTY(devices, get_devices); - protected: LightMutex _lock; diff --git a/panda/src/device/inputDeviceNode.cxx b/panda/src/device/inputDeviceNode.cxx index 0f22cbe7f2..712d377562 100644 --- a/panda/src/device/inputDeviceNode.cxx +++ b/panda/src/device/inputDeviceNode.cxx @@ -54,9 +54,27 @@ get_device() const { void InputDeviceNode:: do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { + + if (_device == nullptr && !_device->is_connected()) { + return; + } + + _device->poll(); + // get all button events of the device and forward them to the data graph if (_device->has_button_event()) { PT(ButtonEventList) bel = _device->get_button_events(); + + // Register the current state for each button. + for (int i = 0; i < bel->get_num_events(); ++i) { + const ButtonEvent &event = bel->get_event(i); + if (event._type == ButtonEvent::T_down) { + _button_states[event._button] = true; + } else if (event._type == ButtonEvent::T_down) { + _button_states[event._button] = false; + } + } + output.set_data(_button_events_output, EventParameter(bel)); } diff --git a/panda/src/device/inputDeviceNode.h b/panda/src/device/inputDeviceNode.h index 659ed5bf71..8e0d20e96a 100644 --- a/panda/src/device/inputDeviceNode.h +++ b/panda/src/device/inputDeviceNode.h @@ -21,17 +21,22 @@ #include "linmath_events.h" /** - * Reads the controler data sent from the InputDeviceManager, and - * transmits it down the data graph. - * + * Reads the controller data sent from the InputDeviceManager, and transmits + * it down the data graph. * + * This is intended to only be accessed from the app thread. */ class EXPCL_PANDA_DEVICE InputDeviceNode : public DataNode { PUBLISHED: InputDeviceNode(InputDevice *device, const std::string &name); + +public: void set_device(InputDevice *device); PT(InputDevice) get_device() const; +PUBLISHED: + MAKE_PROPERTY(device, get_device, set_device); + protected: // Inherited from DataNode virtual void do_transmit_data(DataGraphTraverser *trav, @@ -39,6 +44,9 @@ protected: DataNodeTransmit &output); private: + pmap _button_states; + pmap _control_states; + // outputs int _button_events_output; int _low_battery_event_output; diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index 0f0078404b..b3505e084f 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -287,14 +287,14 @@ parse_element(IOHIDElementRef element) { int max = IOHIDElementGetLogicalMax(element); if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. - add_control(axis, max, min, true); + add_axis(axis, max, min, true); } else if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { // We'd like to reverse the Y axis to match the XInput behavior. // We also reverse yaw to obey the right-hand rule. - add_control(axis, max, min); + add_axis(axis, max, min); } else { - add_control(axis, min, max); + add_axis(axis, min, max); } _analog_elements.push_back(element); @@ -734,7 +734,7 @@ do_poll() { IOHIDValueRef value_ref; if (IOHIDDeviceGetValue(_device, _analog_elements[i], &value_ref) == kIOReturnSuccess) { int value = IOHIDValueGetIntegerValue(value_ref); - control_changed(i, value); + axis_changed(i, value); } } diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index e0f4d90348..ccdd29d553 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -121,7 +121,7 @@ open_device() { ioctl(_fd, JSIOCGBUTTONS, &num_buttons); _buttons.resize(num_buttons); - _controls.resize(num_axes); + _axes.resize(num_axes); if (num_buttons > 0) { uint16_t btnmap[512]; @@ -225,11 +225,11 @@ open_device() { _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); if (_device_class == DC_gamepad) { - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + add_button(GamepadButton::dpad_left()); + add_button(GamepadButton::dpad_right()); } else { - _buttons.push_back(ButtonState(GamepadButton::hat_left())); - _buttons.push_back(ButtonState(GamepadButton::hat_right())); + add_button(GamepadButton::hat_left()); + add_button(GamepadButton::hat_right()); } axis = Axis::none; } @@ -241,11 +241,11 @@ open_device() { _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); if (_device_class == DC_gamepad) { - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + add_button(GamepadButton::dpad_up()); + add_button(GamepadButton::dpad_down()); } else { - _buttons.push_back(ButtonState(GamepadButton::hat_up())); - _buttons.push_back(ButtonState(GamepadButton::hat_down())); + add_button(GamepadButton::hat_up()); + add_button(GamepadButton::hat_down()); } axis = Axis::none; } @@ -259,28 +259,28 @@ open_device() { axis = Axis::none; break; } - _controls[i].axis = axis; + _axes[i].axis = axis; if (axis == Axis::left_trigger || axis == Axis::right_trigger) { // We'd like to use 0.0 to indicate the resting position. - _controls[i]._scale = 1.0 / 65534.0; - _controls[i]._bias = 0.5; + _axes[i]._scale = 1.0 / 65534.0; + _axes[i]._bias = 0.5; have_analog_triggers = true; } else if (axis == Axis::left_y || axis == Axis::right_y || axis == Axis::y) { - _controls[i]._scale = 1.0 / -32767.0; - _controls[i]._bias = 0.0; + _axes[i]._scale = 1.0 / -32767.0; + _axes[i]._bias = 0.0; } else { - _controls[i]._scale = 1.0 / 32767.0; - _controls[i]._bias = 0.0; + _axes[i]._scale = 1.0 / 32767.0; + _axes[i]._bias = 0.0; } } } if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) { // Emulate analog triggers. - _ltrigger_control = (int)_controls.size(); - add_control(Axis::left_trigger, 0, 1, false); - add_control(Axis::right_trigger, 0, 1, false); + _ltrigger_control = (int)_axes.size(); + add_axis(Axis::left_trigger, 0, 1, false); + add_axis(Axis::right_trigger, 0, 1, false); } else { _ltrigger_button = -1; _rtrigger_button = -1; @@ -343,8 +343,8 @@ open_device() { // are all 0, which indicates that the driver hasn't received any data for // this gamepad yet (which means it hasn't been plugged in for this session) if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) { - for (const auto &control : _controls) { - if (control.state != 0.0) { + for (const auto &control : _axes) { + if (control.value != 0.0) { _is_connected = true; return true; } @@ -398,9 +398,9 @@ process_events() { if (events[i].type & JS_EVENT_BUTTON) { if (index == _ltrigger_button) { - control_changed(_ltrigger_control, events[i].value); + axis_changed(_ltrigger_control, events[i].value); } else if (index == _rtrigger_button) { - control_changed(_ltrigger_control + 1, events[i].value); + axis_changed(_ltrigger_control + 1, events[i].value); } button_changed(index, (events[i].value != 0)); @@ -413,7 +413,7 @@ process_events() { button_changed(_dpad_up_button+1, events[i].value > 1000); } - control_changed(index, events[i].value); + axis_changed(index, events[i].value); } } diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index d61ad04d6e..7aa520a1fe 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -362,11 +362,11 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { gamepad_buttons = gamepad_buttons_snes; } - // Prepare a mapping of data indices to button/control indices. + // Prepare a mapping of data indices to button/axis indices. _indices.resize(caps.NumberInputDataIndices); _buttons.clear(); - _controls.clear(); + _axes.clear(); USHORT num_button_caps = caps.NumberInputButtonCaps; PHIDP_BUTTON_CAPS button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS)); @@ -553,17 +553,17 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; } - int control_index; + int axis_index; if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. - control_index = add_control(axis, cap.LogicalMax, cap.LogicalMin, true); + axis_index = add_axis(axis, cap.LogicalMax, cap.LogicalMin, true); } else if (!is_signed) { // All axes on the weird XInput-style mappings go from -1 to 1 - control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax, true); + axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax, true); } else { - control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax); + axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax); } - _indices[data_index] = Index::control(control_index, is_signed); + _indices[data_index] = Index::axis(axis_index, is_signed); } } @@ -635,11 +635,11 @@ on_input(PRAWINPUT input) { for (ULONG di = 0; di < count; ++di) { if (data[di].DataIndex != _hat_data_index) { const Index &idx = _indices[data[di].DataIndex]; - if (idx._control >= 0) { + if (idx._axis >= 0) { if (idx._signed) { - control_changed(idx._control, (SHORT)data[di].RawValue); + axis_changed(idx._axis, (SHORT)data[di].RawValue); } else { - control_changed(idx._control, data[di].RawValue); + axis_changed(idx._axis, data[di].RawValue); } } if (idx._button >= 0) { diff --git a/panda/src/device/winRawInputDevice.h b/panda/src/device/winRawInputDevice.h index 95ddc59cc5..c5cba8c615 100644 --- a/panda/src/device/winRawInputDevice.h +++ b/panda/src/device/winRawInputDevice.h @@ -49,28 +49,28 @@ private: // Indexed by report ID pvector _report_buttons; - // Either a button index or a control index. + // Either a button index or a axis index. struct Index { - Index() : _button(-1), _control(-1) {} + Index() : _button(-1), _axis(-1) {} static Index button(int index) { Index idx; idx._button = index; return idx; } - static Index control(int index, bool is_signed=true) { + static Index axis(int index, bool is_signed=true) { Index idx; - idx._control = index; + idx._axis = index; idx._signed = is_signed; return idx; } int _button; - int _control; + int _axis; bool _signed; }; - // Maps a "data index" to either button index or control index. + // Maps a "data index" to either button index or axis index. pvector _indices; int _hat_data_index; int _hat_data_minimum; diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 680bbd26dd..40cb56959c 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -126,7 +126,7 @@ XInputDevice(DWORD user_index) : nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT); - _controls.resize(6); + _axes.resize(6); _buttons.resize(16); } @@ -301,6 +301,7 @@ init_xinput() { /** * Initializes the device. Called when the device was just connected. + * Assumes either the lock is held or this is called from the constructor. */ void XInputDevice:: init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { @@ -315,81 +316,81 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { default: case XINPUT_DEVSUBTYPE_GAMEPAD: _device_class = DC_gamepad; - set_control_map(0, Axis::left_trigger); - set_control_map(1, Axis::right_trigger); - set_control_map(2, Axis::left_x); - set_control_map(3, Axis::left_y); - set_control_map(4, Axis::right_x); - set_control_map(5, Axis::right_y); + _axes[0].axis = Axis::left_trigger; + _axes[1].axis = Axis::right_trigger; + _axes[2].axis = Axis::left_x; + _axes[3].axis = Axis::left_y; + _axes[4].axis = Axis::right_x; + _axes[5].axis = Axis::right_y; break; case XINPUT_DEVSUBTYPE_WHEEL: _device_class = DC_steering_wheel; - set_control_map(0, Axis::brake); - set_control_map(1, Axis::accelerator); - set_control_map(2, Axis::wheel); - set_control_map(3, Axis::none); - set_control_map(4, Axis::none); - set_control_map(5, Axis::none); + _axes[0].axis = Axis::brake; + _axes[1].axis = Axis::accelerator; + _axes[2].axis = Axis::wheel; + _axes[3].axis = Axis::none; + _axes[4].axis = Axis::none; + _axes[5].axis = Axis::none; break; case XINPUT_DEVSUBTYPE_FLIGHT_STICK: _device_class = DC_flight_stick; - set_control_map(0, Axis::yaw); - set_control_map(1, Axis::throttle); - set_control_map(2, Axis::roll); - set_control_map(3, Axis::pitch); - set_control_map(4, Axis::none); - set_control_map(5, Axis::none); + _axes[0].axis = Axis::yaw; + _axes[1].axis = Axis::throttle; + _axes[2].axis = Axis::roll; + _axes[3].axis = Axis::pitch; + _axes[4].axis = Axis::none; + _axes[5].axis = Axis::none; break; case XINPUT_DEVSUBTYPE_DANCE_PAD: _device_class = DC_dance_pad; - set_control_map(0, Axis::none); - set_control_map(1, Axis::none); - set_control_map(2, Axis::none); - set_control_map(3, Axis::none); - set_control_map(4, Axis::none); - set_control_map(5, Axis::none); + _axes[0].axis = Axis::none; + _axes[1].axis = Axis::none; + _axes[2].axis = Axis::none; + _axes[3].axis = Axis::none; + _axes[4].axis = Axis::none; + _axes[5].axis = Axis::none; break; } - _controls[0]._scale = 1.0 / 255.0; - _controls[1]._scale = 1.0 / 255.0; - _controls[2]._scale = 1.0 / 32767.5; - _controls[3]._scale = 1.0 / 32767.5; - _controls[4]._scale = 1.0 / 32767.5; - _controls[5]._scale = 1.0 / 32767.5; + _axes[0]._scale = 1.0 / 255.0; + _axes[1]._scale = 1.0 / 255.0; + _axes[2]._scale = 1.0 / 32767.5; + _axes[3]._scale = 1.0 / 32767.5; + _axes[4]._scale = 1.0 / 32767.5; + _axes[5]._scale = 1.0 / 32767.5; - _controls[2]._bias = 0.5 / 32767.5; - _controls[3]._bias = 0.5 / 32767.5; - _controls[4]._bias = 0.5 / 32767.5; - _controls[5]._bias = 0.5 / 32767.5; + _axes[2]._bias = 0.5 / 32767.5; + _axes[3]._bias = 0.5 / 32767.5; + _axes[4]._bias = 0.5 / 32767.5; + _axes[5]._bias = 0.5 / 32767.5; if (caps.Flags & XINPUT_CAPS_NO_NAVIGATION) { - set_button_map(0, ButtonHandle::none()); - set_button_map(1, ButtonHandle::none()); - set_button_map(2, ButtonHandle::none()); - set_button_map(3, ButtonHandle::none()); - set_button_map(4, ButtonHandle::none()); - set_button_map(5, ButtonHandle::none()); + _buttons[0].handle = ButtonHandle::none(); + _buttons[1].handle = ButtonHandle::none(); + _buttons[2].handle = ButtonHandle::none(); + _buttons[3].handle = ButtonHandle::none(); + _buttons[4].handle = ButtonHandle::none(); + _buttons[5].handle = ButtonHandle::none(); } else { - set_button_map(0, GamepadButton::dpad_up()); - set_button_map(1, GamepadButton::dpad_down()); - set_button_map(2, GamepadButton::dpad_left()); - set_button_map(3, GamepadButton::dpad_right()); - set_button_map(4, GamepadButton::start()); - set_button_map(5, GamepadButton::back()); + _buttons[0].handle = GamepadButton::dpad_up(); + _buttons[1].handle = GamepadButton::dpad_down(); + _buttons[2].handle = GamepadButton::dpad_left(); + _buttons[3].handle = GamepadButton::dpad_right(); + _buttons[4].handle = GamepadButton::start(); + _buttons[5].handle = GamepadButton::back(); } - set_button_map(6, GamepadButton::lstick()); - set_button_map(7, GamepadButton::rstick()); - set_button_map(8, GamepadButton::lshoulder()); - set_button_map(9, GamepadButton::rshoulder()); - set_button_map(10, GamepadButton::guide()); - set_button_map(11, GamepadButton::face_a()); - set_button_map(12, GamepadButton::face_b()); - set_button_map(13, GamepadButton::face_x()); - set_button_map(14, GamepadButton::face_y()); + _buttons[6].handle = GamepadButton::lstick(); + _buttons[7].handle = GamepadButton::rstick(); + _buttons[8].handle = GamepadButton::lshoulder(); + _buttons[9].handle = GamepadButton::rshoulder(); + _buttons[10].handle = GamepadButton::guide(); + _buttons[11].handle = GamepadButton::face_a(); + _buttons[12].handle = GamepadButton::face_b(); + _buttons[13].handle = GamepadButton::face_x(); + _buttons[14].handle = GamepadButton::face_y(); if (caps.Vibration.wLeftMotorSpeed != 0 || caps.Vibration.wRightMotorSpeed != 0) { @@ -403,8 +404,8 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { batt.BatteryType != BATTERY_TYPE_WIRED) { // This device has a battery. Report the battery level. _flags |= IDF_has_battery; - _battery_level = batt.BatteryLevel; - _max_battery_level = BATTERY_LEVEL_FULL; + _battery_data.level = batt.BatteryLevel; + _battery_data.max_level = BATTERY_LEVEL_FULL; } } } @@ -413,7 +414,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { WORD mask = 1; for (int i = 0; i < 16; ++i) { // Set the state without triggering a button event. - _buttons[i].state = (buttons & mask) ? S_down : S_up; + _buttons[i]._state = (buttons & mask) ? S_down : S_up; mask <<= 1; if (i == 10) { // XInput skips 0x0800. @@ -421,12 +422,12 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { } } - control_changed(0, state.Gamepad.bLeftTrigger); - control_changed(1, state.Gamepad.bRightTrigger); - control_changed(2, state.Gamepad.sThumbLX); - control_changed(3, state.Gamepad.sThumbLY); - control_changed(4, state.Gamepad.sThumbRX); - control_changed(5, state.Gamepad.sThumbRY); + axis_changed(0, state.Gamepad.bLeftTrigger); + axis_changed(1, state.Gamepad.bRightTrigger); + axis_changed(2, state.Gamepad.sThumbLX); + axis_changed(3, state.Gamepad.sThumbLY); + axis_changed(4, state.Gamepad.sThumbRX); + axis_changed(5, state.Gamepad.sThumbRY); _last_buttons = buttons; _last_packet = state.dwPacketNumber; @@ -491,12 +492,12 @@ do_poll() { } } - control_changed(0, state.Gamepad.bLeftTrigger); - control_changed(1, state.Gamepad.bRightTrigger); - control_changed(2, state.Gamepad.sThumbLX); - control_changed(3, state.Gamepad.sThumbLY); - control_changed(4, state.Gamepad.sThumbRX); - control_changed(5, state.Gamepad.sThumbRY); + axis_changed(0, state.Gamepad.bLeftTrigger); + axis_changed(1, state.Gamepad.bRightTrigger); + axis_changed(2, state.Gamepad.sThumbLX); + axis_changed(3, state.Gamepad.sThumbLY); + axis_changed(4, state.Gamepad.sThumbRX); + axis_changed(5, state.Gamepad.sThumbRY); _last_buttons = state.Gamepad.wButtons; _last_packet = state.dwPacketNumber; diff --git a/panda/src/vrpn/vrpnAnalog.cxx b/panda/src/vrpn/vrpnAnalog.cxx index c24beabb48..2745aab75e 100644 --- a/panda/src/vrpn/vrpnAnalog.cxx +++ b/panda/src/vrpn/vrpnAnalog.cxx @@ -100,13 +100,7 @@ vrpn_analog_callback(void *userdata, const vrpn_ANALOGCB info) { for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnAnalogDevice *device = (*di); for (int i = 0; i < info.num_channel; i++) { - if (vrpn_cat.is_debug()) { - if (device->get_control_state(i) != info.channel[i]) { - vrpn_cat.debug() - << *self << " got analog " << i << " = " << info.channel[i] << "\n"; - } - } - device->set_control_state(i, info.channel[i]); + device->set_axis_value(i, info.channel[i]); } } } diff --git a/panda/src/vrpn/vrpnTracker.cxx b/panda/src/vrpn/vrpnTracker.cxx index b42f4f5336..e1b08e2a72 100644 --- a/panda/src/vrpn/vrpnTracker.cxx +++ b/panda/src/vrpn/vrpnTracker.cxx @@ -107,10 +107,10 @@ vrpn_position_callback(void *userdata, const vrpn_TRACKERCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_position) { - device->set_tracker(LPoint3(info.pos[0], info.pos[1], info.pos[2]), - LOrientation(info.quat[3], info.quat[0], - info.quat[1], info.quat[2]), - VrpnClient::convert_to_secs(info.msg_time)); + device->tracker_changed(LPoint3(info.pos[0], info.pos[1], info.pos[2]), + LOrientation(info.quat[3], info.quat[0], + info.quat[1], info.quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } @@ -132,10 +132,10 @@ vrpn_velocity_callback(void *userdata, const vrpn_TRACKERVELCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_velocity) { - device->set_tracker(LPoint3(info.vel[0], info.vel[1], info.vel[2]), - LOrientation(info.vel_quat[3], info.vel_quat[0], - info.vel_quat[1], info.vel_quat[2]), - VrpnClient::convert_to_secs(info.msg_time)); + device->tracker_changed(LPoint3(info.vel[0], info.vel[1], info.vel[2]), + LOrientation(info.vel_quat[3], info.vel_quat[0], + info.vel_quat[1], info.vel_quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } @@ -157,10 +157,10 @@ vrpn_acceleration_callback(void *userdata, const vrpn_TRACKERACCCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_acceleration) { - device->set_tracker(LPoint3(info.acc[0], info.acc[1], info.acc[2]), - LOrientation(info.acc_quat[3], info.acc_quat[0], - info.acc_quat[1], info.acc_quat[2]), - VrpnClient::convert_to_secs(info.msg_time)); + device->tracker_changed(LPoint3(info.acc[0], info.acc[1], info.acc[2]), + LOrientation(info.acc_quat[3], info.acc_quat[0], + info.acc_quat[1], info.acc_quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } From 76365f3ed19318400e172e7e5c4ea7c2e62e0ac8 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Sep 2018 20:22:40 +0200 Subject: [PATCH 65/82] showbase: make input device binding explicit, not automatic --- direct/src/showbase/ShowBase.py | 109 ++++++++++++-------------------- 1 file changed, 41 insertions(+), 68 deletions(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 5a7a647c0a..8ed91bd8af 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -307,12 +307,7 @@ class ShowBase(DirectObject.DirectObject): ## This is the global input device manager, which keeps track of ## connected input devices. self.devices = InputDeviceManager.getGlobalPtr() - # add existing devices to the data graph - for device in self.devices.devices: - self.connectDevice(device) - # Checks for device connection and disconnection - self.accept('connect-device', self.connectDevice) - self.accept('disconnect-device', self.disconnectDevice) + self.__inputDeviceNodes = {} self.createStats() @@ -1681,76 +1676,56 @@ class ShowBase(DirectObject.DirectObject): return self.mouseWatcherNode.getModifierButtons().isDown( KeyboardButton.meta()) - def connectDevice(self, device): + def attachInputDevice(self, device, prefix=None): """ - This function will get called each time a new device got - connected and will add that new device to the data graph. + This function attaches an input device to the data graph, which will + cause the device to be polled and generate events. If a prefix is + given and not None, it is used to prefix events generated by this + device, separated by a hyphen. - Each device class will get a specific prefix for thrown events. Those - are currently as follow - - gamepad - flight_stick - steering_wheel - dance_pad - mouse - keyboard - unclassified_device - - In addition, the index of that device will appended to the prefix, - so for example if you hit the A button of the first connected gamepad - you will get an event like "gamepad0-action_a" the second gamepad will - then be catchable via "gamepad1-button_event" and so on. - Note, each device class will have a separate 0 based index, this way - you can have a gamepad0 as well as a steering_wheel0 and flight_stick0. - - All newly created button throwers will be stored in - the deviceButtonThrowers lsit + If you call this, you should consider calling detachInputDevice when + you are done with the device or when it is disconnected. """ - idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.getName())) - prefix = "unclassified_device" - if device.getDeviceClass() == InputDevice.DC_gamepad: - prefix = "gamepad" - elif device.getDeviceClass() == InputDevice.DC_flight_stick: - prefix = "flight_stick" - elif device.getDeviceClass() == InputDevice.DC_steering_wheel: - prefix = "steering_wheel" - elif device.getDeviceClass() == InputDevice.DC_dance_pad: - prefix = "dance_pad" - elif device.getDeviceClass() == InputDevice.DC_mouse: - prefix = "mouse" - elif device.getDeviceClass() == InputDevice.DC_keyboard: - prefix = "keyboard" - currentPrefixes = [] - for np in self.dataRoot.findAllMatches("**/{}".format(prefix)): - bt = np.node() - currentPrefixes.append(bt.getPrefix()) + # Protect against the same device being attached multiple times. + assert device not in self.__inputDeviceNodes - id = 0 - # Find the next free ID for the newly connected device - while "{}{}-".format(prefix, id) in currentPrefixes: - id+=1 - # Setup the button thrower for that device and register it's event prefix - bt = idn.attachNewNode(ButtonThrower(prefix)) - assert self.notify.debug("Registered event prefix {}{}-".format(prefix, id)) - bt.node().setPrefix("{}{}-".format(prefix, id)) - # append the new button thrower to the list of device button throwers + idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name)) + + # Setup the button thrower to generate events for the device. + bt = idn.attachNewNode(ButtonThrower(device.name)) + if prefix is not None: + bt.node().setPrefix(prefix + '-') + + assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix)) + self.__inputDeviceNodes[device] = idn self.deviceButtonThrowers.append(bt) - def disconnectDevice(self, device): + def detachInputDevice(self, device): """ - This function will get called each time a new device got - connected. It is then used to clean up the given device from the - data graph. + This should be called after attaching an input device using + attachInputDevice and the device is disconnected or you no longer wish + to keep polling this device for events. + + You do not strictly need to call this if you expect the device to be + reconnected (but be careful that you don't reattach it). """ - self.notify.debug("Disconnect device {}".format(device.getName())) - idn = self.dataRoot.find("**/{}".format(device.getName())) - for bt in list(self.deviceButtonThrowers): - if bt.getName() == idn.getName(): + + if device not in self.__inputDeviceNodes: + assert device in self.__inputDeviceNodes + return + + assert self.notify.debug("Detached device {0}".format(device.name)) + + # Remove the ButtonThrower from the deviceButtonThrowers list. + idn = self.__inputDeviceNodes[device] + for bt in self.deviceButtonThrowers: + if idn.isAncestorOf(bt): self.deviceButtonThrowers.remove(bt) break + idn.removeNode() + del self.__inputDeviceNodes[device] def addAngularIntegrator(self): if not self.physicsMgrAngular: @@ -1944,10 +1919,6 @@ class ShowBase(DirectObject.DirectObject): # Check if there were newly connected devices. self.devices.update() - # Poll all connected devices. - for device in self.devices.devices: - device.poll() - # traverse the data graph. This reads all the control # inputs (from the mouse and keyboard, for instance) and also # directly acts upon them (for instance, to move the avatar). @@ -3163,6 +3134,8 @@ class ShowBase(DirectObject.DirectObject): setup_mouse = setupMouse setup_mouse_cb = setupMouseCB enable_software_mouse_pointer = enableSoftwareMousePointer + detach_input_device = detachInputDevice + attach_input_device = attachInputDevice add_angular_integrator = addAngularIntegrator enable_particles = enableParticles disable_particles = disableParticles From 9b85a288616be32dfbb0060b8ce9b435c6beff6d Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Sep 2018 16:53:00 +0200 Subject: [PATCH 66/82] putil: rename MouseData to PointerData to be more inclusive --- panda/src/device/inputDevice.h | 4 +- panda/src/display/mouseAndKeyboard.cxx | 2 +- panda/src/putil/mouseData.h | 38 +++----------- panda/src/putil/p3putil_composite2.cxx | 2 +- .../src/putil/{mouseData.I => pointerData.I} | 22 ++++---- .../putil/{mouseData.cxx => pointerData.cxx} | 10 ++-- panda/src/putil/pointerData.h | 51 +++++++++++++++++++ panda/src/x11display/x11GraphicsWindow.cxx | 4 +- 8 files changed, 79 insertions(+), 54 deletions(-) rename panda/src/putil/{mouseData.I => pointerData.I} (68%) rename panda/src/putil/{mouseData.cxx => pointerData.cxx} (70%) create mode 100644 panda/src/putil/pointerData.h diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 7e4d4f623b..10ad2c8177 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -20,7 +20,7 @@ #include "buttonEventList.h" #include "pointerEvent.h" #include "pointerEventList.h" -#include "mouseData.h" +#include "pointerData.h" #include "trackerData.h" #include "clockObject.h" @@ -29,8 +29,6 @@ #include "lightMutex.h" #include "lightMutexHolder.h" -typedef MouseData PointerData; - /** * This is a structure representing a single input device. Input devices may * have zero or more buttons, pointers, or axes associated with them, and diff --git a/panda/src/display/mouseAndKeyboard.cxx b/panda/src/display/mouseAndKeyboard.cxx index 8243ef4759..97c6447bdb 100644 --- a/panda/src/display/mouseAndKeyboard.cxx +++ b/panda/src/display/mouseAndKeyboard.cxx @@ -99,7 +99,7 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, output.set_data(_pixel_size_output, EventParameter(_pixel_size)); if (device->has_pointer()) { - const MouseData &mdata = device->get_pointer(); + PointerData mdata = device->get_pointer(); if (mdata._in_window) { // Get mouse motion in pixels. diff --git a/panda/src/putil/mouseData.h b/panda/src/putil/mouseData.h index 0d2a294568..2747c26061 100644 --- a/panda/src/putil/mouseData.h +++ b/panda/src/putil/mouseData.h @@ -7,45 +7,21 @@ * with this source code in a file named "LICENSE." * * @file mouseData.h - * @author drose - * @date 1999-02-08 + * @author rdb + * @date 2018-09-24 */ #ifndef MOUSEDATA_H #define MOUSEDATA_H -#include "pandabase.h" - -#include "modifierButtons.h" +#include "pointerData.h" +BEGIN_PUBLISH /** - * Holds the data that might be generated by a 2-d pointer input device, such - * as the mouse in the GraphicsWindow. + * Deprecated alias for PointerData. */ -class EXPCL_PANDA_PUTIL MouseData { -PUBLISHED: - INLINE MouseData(); - INLINE MouseData(const MouseData ©); - INLINE void operator = (const MouseData ©); +typedef PointerData MouseData; - INLINE double get_x() const; - INLINE double get_y() const; - INLINE bool get_in_window() const; - - void output(std::ostream &out) const; - - MAKE_PROPERTY(x, get_x); - MAKE_PROPERTY(y, get_y); - MAKE_PROPERTY(in_window, get_in_window); - -public: - bool _in_window; - double _xpos; - double _ypos; -}; - -INLINE std::ostream &operator << (std::ostream &out, const MouseData &md); - -#include "mouseData.I" +END_PUBLISH #endif diff --git a/panda/src/putil/p3putil_composite2.cxx b/panda/src/putil/p3putil_composite2.cxx index 9b0f01d851..80ae7450bb 100644 --- a/panda/src/putil/p3putil_composite2.cxx +++ b/panda/src/putil/p3putil_composite2.cxx @@ -9,11 +9,11 @@ #include "loaderOptions.cxx" #include "modifierButtons.cxx" #include "mouseButton.cxx" -#include "mouseData.cxx" #include "nameUniquifier.cxx" #include "nodeCachedReferenceCount.cxx" #include "paramValue.cxx" #include "pbitops.cxx" +#include "pointerData.cxx" #include "pta_ushort.cxx" #include "simpleHashMap.cxx" #include "sparseArray.cxx" diff --git a/panda/src/putil/mouseData.I b/panda/src/putil/pointerData.I similarity index 68% rename from panda/src/putil/mouseData.I rename to panda/src/putil/pointerData.I index c0ab6997ef..6697c31d1a 100644 --- a/panda/src/putil/mouseData.I +++ b/panda/src/putil/pointerData.I @@ -6,7 +6,7 @@ * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * - * @file mouseData.I + * @file pointerData.I * @author drose * @date 2002-07-15 */ @@ -14,8 +14,8 @@ /** * */ -INLINE MouseData:: -MouseData() { +INLINE PointerData:: +PointerData() { _in_window = false; _xpos = 0; _ypos = 0; @@ -24,8 +24,8 @@ MouseData() { /** * */ -INLINE MouseData:: -MouseData(const MouseData ©) : +INLINE PointerData:: +PointerData(const PointerData ©) : _in_window(copy._in_window), _xpos(copy._xpos), _ypos(copy._ypos) @@ -35,8 +35,8 @@ MouseData(const MouseData ©) : /** * */ -INLINE void MouseData:: -operator = (const MouseData ©) { +INLINE void PointerData:: +operator = (const PointerData ©) { _in_window = copy._in_window; _xpos = copy._xpos; _ypos = copy._ypos; @@ -45,7 +45,7 @@ operator = (const MouseData ©) { /** * */ -INLINE double MouseData:: +INLINE double PointerData:: get_x() const { return _xpos; } @@ -53,7 +53,7 @@ get_x() const { /** * */ -INLINE double MouseData:: +INLINE double PointerData:: get_y() const { return _ypos; } @@ -61,13 +61,13 @@ get_y() const { /** * */ -INLINE bool MouseData:: +INLINE bool PointerData:: get_in_window() const { return _in_window; } -INLINE std::ostream &operator << (std::ostream &out, const MouseData &md) { +INLINE std::ostream &operator << (std::ostream &out, const PointerData &md) { md.output(out); return out; } diff --git a/panda/src/putil/mouseData.cxx b/panda/src/putil/pointerData.cxx similarity index 70% rename from panda/src/putil/mouseData.cxx rename to panda/src/putil/pointerData.cxx index de83bd1505..ccf8e9d9f9 100644 --- a/panda/src/putil/mouseData.cxx +++ b/panda/src/putil/pointerData.cxx @@ -6,21 +6,21 @@ * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * - * @file mouseData.cxx + * @file pointerData.cxx * @author drose * @date 1999-02-08 */ -#include "mouseData.h" +#include "pointerData.h" /** * */ -void MouseData:: +void PointerData:: output(std::ostream &out) const { if (!_in_window) { - out << "MouseData: Not in window"; + out << "PointerData: Not in window"; } else { - out << "MouseData: (" << _xpos << ", " << _ypos << ")"; + out << "PointerData: (" << _xpos << ", " << _ypos << ")"; } } diff --git a/panda/src/putil/pointerData.h b/panda/src/putil/pointerData.h new file mode 100644 index 0000000000..aa3f3aeb62 --- /dev/null +++ b/panda/src/putil/pointerData.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pointerData.h + * @author drose + * @date 1999-02-08 + */ + +#ifndef POINTERDATA_H +#define POINTERDATA_H + +#include "pandabase.h" + +#include "modifierButtons.h" + +/** + * Holds the data that might be generated by a 2-d pointer input device, such + * as the mouse in the GraphicsWindow. + */ +class EXPCL_PANDA_PUTIL PointerData { +PUBLISHED: + INLINE PointerData(); + INLINE PointerData(const PointerData ©); + INLINE void operator = (const PointerData ©); + + INLINE double get_x() const; + INLINE double get_y() const; + INLINE bool get_in_window() const; + + void output(std::ostream &out) const; + + MAKE_PROPERTY(x, get_x); + MAKE_PROPERTY(y, get_y); + MAKE_PROPERTY(in_window, get_in_window); + +public: + bool _in_window; + double _xpos; + double _ypos; +}; + +INLINE std::ostream &operator << (std::ostream &out, const PointerData &md); + +#include "pointerData.I" + +#endif diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index b9fa562c64..6297e38758 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -354,7 +354,7 @@ process_events() { case MotionNotify: if (_dga_mouse_enabled) { - const MouseData &md = _input->get_pointer(); + PointerData md = _input->get_pointer(); _input->set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root); } else { _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y); @@ -376,7 +376,7 @@ process_events() { case EnterNotify: if (_dga_mouse_enabled) { - const MouseData &md = _input->get_pointer(); + PointerData md = _input->get_pointer(); _input->set_pointer_in_window(md.get_x(), md.get_y()); } else { _input->set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); From 85752b06f5614d67f20754801a9f96b466d8fa7d Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Sep 2018 17:20:50 +0200 Subject: [PATCH 67/82] device: delete InputDevice copy ctor / assign operator --- panda/src/device/inputDevice.cxx | 16 ------------- panda/src/device/inputDevice.h | 6 ++--- .../src/display/graphicsWindowInputDevice.cxx | 23 ------------------- panda/src/display/graphicsWindowInputDevice.h | 3 --- 4 files changed, 3 insertions(+), 45 deletions(-) diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index aedfeb8222..742c31b688 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -37,22 +37,6 @@ InputDevice(const std::string &name, DeviceClass dev_class, int flags) : _button_events = new ButtonEventList; } -/** - * - */ -InputDevice:: -InputDevice(const InputDevice ©) { - nassertv(false); -} - -/** - * - */ -void InputDevice:: -operator = (const InputDevice ©) { - nassertv(false); -} - /** * */ diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 10ad2c8177..24b27186e3 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -165,9 +165,9 @@ protected: public: InputDevice(); - InputDevice(const InputDevice ©); - void operator = (const InputDevice ©); - ~InputDevice(); + InputDevice(const InputDevice ©) = delete; + InputDevice &operator = (const InputDevice ©) = delete; + virtual ~InputDevice(); INLINE std::string get_name() const; INLINE std::string get_manufacturer() const; diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index e967216f03..3d7102f06f 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -62,29 +62,6 @@ pointer_and_keyboard(GraphicsWindow *host, const string &name) { GraphicsWindowInputDevice(host, name, IDF_has_pointer | IDF_has_keyboard); } -/** - * - */ -GraphicsWindowInputDevice:: -GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) { - *this = copy; -} - -/** - * - */ -void GraphicsWindowInputDevice:: -operator = (const GraphicsWindowInputDevice ©) { - InputDevice::operator = (copy); -} - -/** - * - */ -GraphicsWindowInputDevice:: -~GraphicsWindowInputDevice() { -} - /** * Records that the indicated button has been depressed. */ diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index 1573618723..1f9d941add 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -35,9 +35,6 @@ public: static PT(GraphicsWindowInputDevice) pointer_and_keyboard(GraphicsWindow *host, const std::string &name); INLINE GraphicsWindowInputDevice(); - GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©); - void operator = (const GraphicsWindowInputDevice ©); - ~GraphicsWindowInputDevice(); PUBLISHED: // The following interface is for the various kinds of GraphicsWindows to From c4b3b558c92f7a30eb060f7d943d787919ab6c4d Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Sep 2018 20:48:50 +0200 Subject: [PATCH 68/82] samples: fixes and improvements to gamepad and mappingGUI sample --- samples/gamepad/gamepad.py | 194 ++++++++----- samples/gamepad/mappingGUI.py | 345 ++++++++++++---------- samples/gamepad/models/xbone-icons.egg | 384 +++++++++++++++++++++++++ samples/gamepad/models/xbone-icons.png | Bin 0 -> 29275 bytes 4 files changed, 714 insertions(+), 209 deletions(-) create mode 100644 samples/gamepad/models/xbone-icons.egg create mode 100644 samples/gamepad/models/xbone-icons.png diff --git a/samples/gamepad/gamepad.py b/samples/gamepad/gamepad.py index 82154838f6..c684c01253 100644 --- a/samples/gamepad/gamepad.py +++ b/samples/gamepad/gamepad.py @@ -9,21 +9,51 @@ move the camera where the right stick will rotate the camera. from direct.showbase.ShowBase import ShowBase from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 +from panda3d.core import TextPropertiesManager from direct.gui.OnscreenText import OnscreenText -loadPrcFileData("", "notify-level-device debug") +loadPrcFileData("", """ + default-fov 60 + notify-level-device debug +""") + +# Informational text in the bottom-left corner. We use the special \5 +# character to embed an image representing the gamepad buttons. +INFO_TEXT = """Move \5lstick\5 to strafe, \5rstick\5 to turn +Press \5ltrigger\5 and \5rtrigger\5 to go down/up +Press \5face_x\5 to reset camera""" + class App(ShowBase): def __init__(self): ShowBase.__init__(self) - # print all events sent through the messenger - self.messenger.toggleVerbose() + # Print all events sent through the messenger + #self.messenger.toggleVerbose() + + # Load the graphics for the gamepad buttons and register them, so that + # we can embed them in our information text. + graphics = loader.loadModel("models/xbone-icons.egg") + mgr = TextPropertiesManager.getGlobalPtr() + for name in ["face_a", "face_b", "face_x", "face_y", "ltrigger", "rtrigger", "lstick", "rstick"]: + graphic = graphics.find("**/" + name) + graphic.setScale(1.5) + mgr.setGraphic(name, graphic) + graphic.setZ(-0.5) + + # Show the informational text in the corner. + self.lblInfo = OnscreenText( + parent = self.a2dBottomLeft, + pos = (0.1, 0.3), + fg = (1, 1, 1, 1), + bg = (0.2, 0.2, 0.2, 0.9), + align = TextNode.A_left, + text = INFO_TEXT) + self.lblInfo.textNode.setCardAsMargin(0.5, 0.5, 0.5, 0.2) self.lblWarning = OnscreenText( text = "No devices found", fg=(1,0,0,1), scale = .25) - self.lblWarning.hide() self.lblAction = OnscreenText( text = "Action", @@ -31,99 +61,133 @@ class App(ShowBase): scale = .15) self.lblAction.hide() - self.checkDevices() + # Is there a gamepad connected? + self.gamepad = None + devices = self.devices.getDevices(InputDevice.DC_gamepad) + if devices: + self.connect(devices[0]) # Accept device dis-/connection events - # NOTE: catching the events here will overwrite the accept in showbase, hence - # we need to forward the event in the functions we set here! self.accept("connect-device", self.connect) self.accept("disconnect-device", self.disconnect) self.accept("escape", exit) - self.accept("gamepad0-start", exit) - self.accept("flight_stick0-start", exit) # Accept button events of the first connected gamepad - self.accept("gamepad0-action_a", self.doAction, extraArgs=[True, "Action"]) - self.accept("gamepad0-action_a-up", self.doAction, extraArgs=[False, "Release"]) - self.accept("gamepad0-action_b", self.doAction, extraArgs=[True, "Action 2"]) - self.accept("gamepad0-action_b-up", self.doAction, extraArgs=[False, "Release"]) + self.accept("gamepad-back", exit) + self.accept("gamepad-start", exit) + self.accept("gamepad-face_x", self.reset) + self.accept("gamepad-face_a", self.action, extraArgs=["face_a"]) + self.accept("gamepad-face_a-up", self.actionUp) + self.accept("gamepad-face_b", self.action, extraArgs=["face_b"]) + self.accept("gamepad-face_b-up", self.actionUp) + self.accept("gamepad-face_y", self.action, extraArgs=["face_y"]) + self.accept("gamepad-face_y-up", self.actionUp) self.environment = loader.loadModel("environment") self.environment.reparentTo(render) - # disable pandas default mouse-camera controls so we can handle the camera - # movements by ourself + # Disable the default mouse-camera controls since we need to handle + # our own camera controls. self.disableMouse() - - # list of connected gamepad devices - gamepads = base.devices.getDevices(InputDevice.DC_gamepad) - - # set the center position of the control sticks - # NOTE: here we assume, that the wheel is centered when the application get started. - # In real world applications, you should notice the user and give him enough time - # to center the wheel until you store the center position of the controler! - self.lxcenter = gamepads[0].findControl(InputDevice.C_left_x).state - self.lycenter = gamepads[0].findControl(InputDevice.C_left_y).state - self.rxcenter = gamepads[0].findControl(InputDevice.C_right_x).state - self.rycenter = gamepads[0].findControl(InputDevice.C_right_y).state - + self.reset() self.taskMgr.add(self.moveTask, "movement update task") def connect(self, device): - # we need to forward the event to the connectDevice function of showbase - self.connectDevice(device) - # Now we can check for ourself - self.checkDevices() + """Event handler that is called when a device is discovered.""" + + # We're only interested if this is a gamepad and we don't have a + # gamepad yet. + if device.device_class == InputDevice.DC_gamepad and not self.gamepad: + print("Found %s" % (device)) + self.gamepad = device + + # Enable this device to ShowBase so that we can receive events. + # We set up the events with a prefix of "gamepad-". + self.attachInputDevice(device, prefix="gamepad") + + # Hide the warning that we have no devices. + self.lblWarning.hide() def disconnect(self, device): - # we need to forward the event to the disconnectDevice function of showbase - self.disconnectDevice(device) - # Now we can check for ourself - self.checkDevices() + """Event handler that is called when a device is removed.""" - def checkDevices(self): - # check if we have gamepad devices connected - if self.devices.get_devices(InputDevice.DC_gamepad): - # we have at least one gamepad device - self.lblWarning.hide() + if self.gamepad != device: + # We don't care since it's not our gamepad. + return + + # Tell ShowBase that the device is no longer needed. + print("Disconnected %s" % (device)) + self.detachInputDevice(device) + self.gamepad = None + + # Do we have any other gamepads? Attach the first other gamepad. + devices = self.devices.getDevices(InputDevice.DC_gamepad) + if devices: + self.connect(devices[0]) else: - # no devices connected + # No devices. Show the warning. self.lblWarning.show() - def doAction(self, showText, text): - if showText and self.lblAction.isHidden(): - self.lblAction.show() - else: - self.lblAction.hide() + def reset(self): + """Reset the camera to the initial position.""" + + self.camera.setPosHpr(0, -200, 10, 0, 0, 0) + + def action(self, button): + # Just show which button has been pressed. + self.lblAction.text = "Pressed \5%s\5" % button + self.lblAction.show() + + def actionUp(self): + # Hide the label showing which button is pressed. + self.lblAction.hide() def moveTask(self, task): dt = globalClock.getDt() - movementVec = Vec3() - gamepads = base.devices.getDevices(InputDevice.DC_gamepad) - if len(gamepads) == 0: - # savety check + if not self.gamepad: return task.cont + strafe_speed = 85 + vert_speed = 50 + turn_speed = 100 + + # If the left stick is pressed, we will go faster. + lstick = self.gamepad.findButton("lstick") + if lstick.pressed: + strafe_speed *= 2.0 + # we will use the first found gamepad # Move the camera left/right - left_x = gamepads[0].findControl(InputDevice.C_left_x) - movementVec.setX(left_x.state - self.lxcenter) - # Move the camera forward/backward - left_y = gamepads[0].findControl(InputDevice.C_left_y) - movementVec.setY(left_y.state - self.lycenter) - # Control the cameras heading - right_x = gamepads[0].findControl(InputDevice.C_right_x) - base.camera.setH(base.camera, 100 * dt * (right_x.state - self.rxcenter)) - # Control the cameras pitch - right_y = gamepads[0].findControl(InputDevice.C_right_y) - base.camera.setP(base.camera, 100 * dt * (right_y.state - self.rycenter)) + strafe = Vec3(0) + left_x = self.gamepad.findAxis(InputDevice.Axis.left_x) + left_y = self.gamepad.findAxis(InputDevice.Axis.left_y) + strafe.x = left_x.value + strafe.y = left_y.value - # calculate movement - base.camera.setX(base.camera, 100 * dt * movementVec.getX()) - base.camera.setY(base.camera, 100 * dt * movementVec.getY()) + # Apply some deadzone, since the sticks don't center exactly at 0 + if strafe.lengthSquared() >= 0.01: + self.camera.setPos(self.camera, strafe * strafe_speed * dt) + + # Use the triggers for the vertical position. + trigger_l = self.gamepad.findAxis(InputDevice.Axis.left_trigger) + trigger_r = self.gamepad.findAxis(InputDevice.Axis.right_trigger) + lift = trigger_r.value - trigger_l.value + self.camera.setZ(self.camera.getZ() + (lift * vert_speed * dt)) + + # Move the camera forward/backward + right_x = self.gamepad.findAxis(InputDevice.Axis.right_x) + right_y = self.gamepad.findAxis(InputDevice.Axis.right_y) + + # Again, some deadzone + if abs(right_x.value) >= 0.1 or abs(right_y.value) >= 0.1: + self.camera.setH(self.camera, turn_speed * dt * -right_x.value) + self.camera.setP(self.camera, turn_speed * dt * right_y.value) + + # Reset the roll so that the camera remains upright. + self.camera.setR(0) return task.cont diff --git a/samples/gamepad/mappingGUI.py b/samples/gamepad/mappingGUI.py index ce9234bd1d..84f26a9f60 100644 --- a/samples/gamepad/mappingGUI.py +++ b/samples/gamepad/mappingGUI.py @@ -16,62 +16,75 @@ from panda3d.core import ( TextNode, Vec2, InputDevice, - loadPrcFileData) + loadPrcFileData, + GamepadButton, + KeyboardButton) # Make sure the textures look crisp on every device that supports # non-power-2 textures loadPrcFileData("", "textures-auto-power-2 #t") -class App(ShowBase): +# How much an axis should have moved for it to register as a movement. +DEAD_ZONE = 0.33 + + +class InputMapping(object): + """A container class for storing a mapping from a string action to either + an axis or a button. You could extend this with additional methods to load + the default mappings from a configuration file. """ + + # Define all the possible actions. + actions = ( + "Move forward", "Move backward", "Move left", "Move right", "Jump", + "Buy", "Use", "Break", "Fix", "Trash", "Change", "Mail", "Upgrade", + ) + def __init__(self): - ShowBase.__init__(self) + self.__map = dict.fromkeys(self.actions) - self.setBackgroundColor(0, 0, 0) - # make the font look nice at a big scale - DGG.getDefaultFont().setPixelsPerUnit(100) + def mapButton(self, action, button): + self.__map[action] = ("button", str(button)) - # a dict of actions and button/axis events - self.gamepadMapping = { - "Move forward":"Left Stick Y", - "Move backward":"Left Stick Y", - "Move left":"Left Stick X", - "Move right":"Left Stick X", - "Jump":"a", - "Action":"b", - "Sprint":"x", - "Map":"y", - "action-1":"c", - "action-2":"d", - "action-3":"e", - "action-4":"f", - "action-5":"g", - "action-6":"h", - "action-7":"i", - "action-8":"j", - "action-9":"k", - "action-10":"l", - "action-11":"m", - } - # this will store the action that we want to remap - self.actionToMap = "" - # this will store the key/axis that we want to asign to an action - self.newActionKey = "" - # this will store the label that needs to be actualized in the list - self.actualizeLabel = None + def mapAxis(self, action, axis): + self.__map[action] = ("axis", axis.name) - # The geometry for our basic buttons - maps = loader.loadModel("models/button_map") - self.buttonGeom = ( - maps.find("**/ready"), - maps.find("**/click"), - maps.find("**/hover"), - maps.find("**/disabled")) + def unmap(self): + self.__map[action] = None - # Create the dialog that asks the user for input on a given - # action to map a key to. - DGG.setDefaultDialogGeom("models/dialog.png") - # setup a dialog to ask for device input - self.dlgInput = OkCancelDialog( + def formatMapping(self, action): + """Returns a string label describing the mapping for a given action, + for displaying in a GUI. """ + mapping = self.__map.get(action) + if not mapping: + return "Unmapped" + + # Format the symbolic string from Panda nicely. In a real-world game, + # you might want to look these up in a translation table, or so. + label = mapping[1].replace('_', ' ').title() + if mapping[0] == "axis": + return "Axis: " + label + else: + return "Button: " + label + + +class ChangeActionDialog(object): + """Encapsulates the UI dialog that opens up when changing a mapping. It + holds the state of which action is being set and which button is pressed + and invokes a callback function when the dialog is exited.""" + + def __init__(self, action, button_geom, command): + # This stores which action we are remapping. + self.action = action + + # This will store the key/axis that we want to asign to an action + self.newInputType = "" + self.newInput = "" + self.setKeyCalled = False + + self.__command = command + + # Initialize the DirectGUI stuff. + self.dialog = OkCancelDialog( dialogName="dlg_device_input", pos=(0, 0, 0.25), text="Hit desired key:", @@ -82,7 +95,7 @@ class App(ShowBase): text_align=TextNode.ACenter, fadeScreen=0.65, frameColor=VBase4(0.3, 0.3, 0.3, 1), - button_geom=self.buttonGeom, + button_geom=button_geom, button_scale=0.15, button_text_scale=0.35, button_text_align=TextNode.ALeft, @@ -93,13 +106,73 @@ class App(ShowBase): button_frameColor=VBase4(0, 0, 0, 0), button_frameSize=VBase4(-1.0, 1.0, -0.25, 0.25), button_pressEffect=False, - command=self.closeDialog) - self.dlgInput.setTransparency(True) - self.dlgInput.configureDialog() - scale = self.dlgInput["image_scale"] - self.dlgInput["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0) - self.dlgInput["text_pos"] = (self.dlgInput["text_pos"][0], self.dlgInput["text_pos"][1] + 0.06) - self.dlgInput.hide() + command=self.onClose) + self.dialog.setTransparency(True) + self.dialog.configureDialog() + scale = self.dialog["image_scale"] + self.dialog["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0) + self.dialog["text_pos"] = (self.dialog["text_pos"][0], self.dialog["text_pos"][1] + 0.06) + + def buttonPressed(self, button): + if any(button.guiItem.getState() == 1 for button in self.dialog.buttonList): + # Ignore events while any of the dialog buttons are active, because + # otherwise we register mouse clicks when the user is trying to + # exit the dialog. + return + + text = str(button).replace('_', ' ').title() + self.dialog["text"] = "New event will be:\n\nButton: " + text + self.newInputType = "button" + self.newInput = button + + def axisMoved(self, axis): + text = axis.name.replace('_', ' ').title() + self.dialog["text"] = "New event will be:\n\nAxis: " + text + self.newInputType = "axis" + self.newInput = axis + + def onClose(self, result): + """Called when the OK or Cancel button is pressed.""" + self.dialog.cleanup() + + # Call the constructor-supplied callback with our new setting, if any. + if self.newInput and result == DGG.DIALOG_OK: + self.__command(self.action, self.newInputType, self.newInput) + else: + # Cancel (or no input was pressed) + self.__command(self.action, None, None) + + +class MappingGUIDemo(ShowBase): + def __init__(self): + ShowBase.__init__(self) + + self.setBackgroundColor(0, 0, 0) + # make the font look nice at a big scale + DGG.getDefaultFont().setPixelsPerUnit(100) + + # Store our mapping, with some sensible defaults. In a real game, you + # will want to load these from a configuration file. + self.mapping = InputMapping() + self.mapping.mapAxis("Move forward", InputDevice.Axis.left_y) + self.mapping.mapAxis("Move backward", InputDevice.Axis.left_y) + self.mapping.mapAxis("Move left", InputDevice.Axis.left_x) + self.mapping.mapAxis("Move right", InputDevice.Axis.left_x) + self.mapping.mapButton("Jump", GamepadButton.face_a()) + self.mapping.mapButton("Use", GamepadButton.face_b()) + self.mapping.mapButton("Break", GamepadButton.face_x()) + self.mapping.mapButton("Fix", GamepadButton.face_y()) + + # The geometry for our basic buttons + maps = loader.loadModel("models/button_map") + self.buttonGeom = ( + maps.find("**/ready"), + maps.find("**/click"), + maps.find("**/hover"), + maps.find("**/disabled")) + + # Change the default dialog skin. + DGG.setDefaultDialogGeom("models/dialog.png") # create a sample title self.textscale = 0.1 @@ -135,6 +208,7 @@ class App(ShowBase): decMaps.find("**/dec_click"), decMaps.find("**/dec_hover"), decMaps.find("**/dec_disabled")) + # create the scrolled frame that will hold our list self.lstActionMap = DirectScrolledFrame( # make the frame occupy the whole window @@ -167,135 +241,116 @@ class App(ShowBase): idx = 0 self.listBGEven = base.loader.loadModel("models/list_item_even") self.listBGOdd = base.loader.loadModel("models/list_item_odd") - for key, value in self.gamepadMapping.items(): - item = self.__makeListItem(key, key, value, idx) + self.actionLabels = {} + for action in self.mapping.actions: + mapped = self.mapping.formatMapping(action) + item = self.__makeListItem(action, mapped, idx) item.reparentTo(self.lstActionMap.getCanvas()) idx += 1 # recalculate the canvas size to set scrollbars if necesary self.lstActionMap["canvasSize"] = ( base.a2dLeft+0.05, base.a2dRight-0.05, - -(len(self.gamepadMapping.keys())*0.1), 0.09) + -(len(self.mapping.actions)*0.1), 0.09) self.lstActionMap.setCanvasSize() - def closeDialog(self, result): - self.dlgInput.hide() - if result == DGG.DIALOG_OK: + def closeDialog(self, action, newInputType, newInput): + """Called in callback when the dialog is closed. newInputType will be + "button" or "axis", or None if the remapping was cancelled.""" + + self.dlgInput = None + + if newInputType is not None: # map the event to the given action - self.gamepadMapping[self.actionToMap] = self.newActionKey + if newInputType == "axis": + self.mapping.mapAxis(action, newInput) + else: + self.mapping.mapButton(action, newInput) + # actualize the label in the list that shows the current # event for the action - self.actualizeLabel["text"] = self.newActionKey + self.actionLabels[action]["text"] = self.mapping.formatMapping(action) # cleanup - self.dlgInput["text"] ="Hit desired key:" - self.actionToMap = "" - self.newActionKey = "" - self.actualizeLabel = None for bt in base.buttonThrowers: + bt.node().setSpecificFlag(True) bt.node().setButtonDownEvent("") for bt in base.deviceButtonThrowers: + bt.node().setSpecificFlag(True) bt.node().setButtonDownEvent("") taskMgr.remove("checkControls") - def changeMapping(self, action, label): - # set the action that we want to map a new key to - self.actionToMap = action - # set the label that needs to be actualized - self.actualizeLabel = label - # show our dialog - self.dlgInput.show() + # Now detach all the input devices. + for device in self.attachedDevices: + base.detachInputDevice(device) + self.attachedDevices.clear() - # catch all button events + def changeMapping(self, action): + # Create the dialog window + self.dlgInput = ChangeActionDialog(action, button_geom=self.buttonGeom, command=self.closeDialog) + + # Attach all input devices. + devices = base.devices.getDevices() + for device in devices: + base.attachInputDevice(device) + self.attachedDevices = devices + + # Disable regular button events on all button event throwers, and + # instead broadcast a generic event. for bt in base.buttonThrowers: + bt.node().setSpecificFlag(False) bt.node().setButtonDownEvent("keyListenEvent") for bt in base.deviceButtonThrowers: + bt.node().setSpecificFlag(False) bt.node().setButtonDownEvent("deviceListenEvent") - self.setKeyCalled = False - self.accept("keyListenEvent", self.setKey) - self.accept("deviceListenEvent", self.setDeviceKey) - # As there are no events thrown for control changes, we set up - # a task to check if the controls got moved - # This list will help us for checking which controls got moved - self.controlStates = {None:{}} + self.accept("keyListenEvent", self.dlgInput.buttonPressed) + self.accept("deviceListenEvent", self.dlgInput.buttonPressed) + + # As there are no events thrown for control changes, we set up a task + # to check if the controls were moved + # This list will help us for checking which controls were moved + self.axisStates = {None: {}} # fill it with all available controls - for device in base.devices.get_devices(): - for ctrl in device.controls: - if device not in self.controlStates.keys(): - self.controlStates.update({device: {ctrl.axis: ctrl.state}}) + for device in devices: + for axis in device.axes: + if device not in self.axisStates.keys(): + self.axisStates.update({device: {axis.axis: axis.value}}) else: - self.controlStates[device].update({ctrl.axis: ctrl.state}) + self.axisStates[device].update({axis.axis: axis.value}) # start the task taskMgr.add(self.watchControls, "checkControls") def watchControls(self, task): # move through all devices and all it's controls - for device in base.devices.get_devices(): - for ctrl in device.controls: - # if a control got changed more than the given puffer zone - if self.controlStates[device][ctrl.axis] + 0.2 < ctrl.state or \ - self.controlStates[device][ctrl.axis] - 0.2 > ctrl.state: + for device in self.attachedDevices: + if device.device_class == InputDevice.DC_mouse: + # Ignore mouse axis movement, or the user can't even navigate + # to the OK/Cancel buttons! + continue + + for axis in device.axes: + # if a control got changed more than the given dead zone + if self.axisStates[device][axis.axis] + DEAD_ZONE < axis.value or \ + self.axisStates[device][axis.axis] - DEAD_ZONE > axis.value: # set the current state in the dict - self.controlStates[device][ctrl.axis] = ctrl.state - # check which axis got moved - if ctrl.axis == InputDevice.C_left_x: - self.setKey("Left Stick X") - elif ctrl.axis == InputDevice.C_left_y: - self.setKey("Left Stick Y") - elif ctrl.axis == InputDevice.C_left_trigger: - self.setKey("Left Trigger") - elif ctrl.axis == InputDevice.C_right_x: - self.setKey("Right Stick X") - elif ctrl.axis == InputDevice.C_right_y: - self.setKey("Right Stick Y") - elif ctrl.axis == InputDevice.C_right_trigger: - self.setKey("Right Trigger") - elif ctrl.axis == InputDevice.C_x: - self.setKey("X") - elif ctrl.axis == InputDevice.C_y: - self.setKey("Y") - elif ctrl.axis == InputDevice.C_trigger: - self.setKey("Trigger") - elif ctrl.axis == InputDevice.C_throttle: - self.setKey("Throttle") - elif ctrl.axis == InputDevice.C_rudder: - self.setKey("Rudder") - elif ctrl.axis == InputDevice.C_hat_x: - self.setKey("Hat X") - elif ctrl.axis == InputDevice.C_hat_y: - self.setKey("Hat Y") - elif ctrl.axis == InputDevice.C_wheel: - self.setKey("Wheel") - elif ctrl.axis == InputDevice.C_accelerator: - self.setKey("Acclerator") - elif ctrl.axis == InputDevice.C_brake: - self.setKey("Break") + self.axisStates[device][axis.axis] = axis.value + + # Format the axis for being displayed. + if axis.axis != InputDevice.Axis.none: + #label = axis.axis.name.replace('_', ' ').title() + self.dlgInput.axisMoved(axis.axis) + return task.cont - def setKey(self, args): - self.setKeyCalled = True - if self.dlgInput.buttonList[0].guiItem.getState() == 1: - # this occurs if the OK button was clicked. To prevent to - # always set the mouse1 event whenever the OK button was - # pressed, we instantly return from this function - return - self.dlgInput["text"] = "New event will be:\n\n" + args - self.newActionKey = args - - def setDeviceKey(self, args): - if not self.setKeyCalled: - self.setKey(args) - self.setKeyCalled = False - - def __makeListItem(self, itemName, action, event, index): + def __makeListItem(self, action, event, index): def dummy(): pass if index % 2 == 0: bg = self.listBGEven else: bg = self.listBGOdd item = DirectFrame( - text=itemName, + text=action, geom=bg, geom_scale=(base.a2dRight-0.05, 1, 0.1), frameSize=VBase4(base.a2dLeft+0.05, base.a2dRight-0.05, -0.05, 0.05), @@ -317,6 +372,8 @@ class App(ShowBase): ) lbl.reparentTo(item) lbl.setTransparency(True) + self.actionLabels[action] = lbl + buttonScale = 0.15 btn = DirectButton( text="Change", @@ -333,10 +390,10 @@ class App(ShowBase): pos=(base.a2dRight-(0.898*buttonScale+0.3), 0, 0), pressEffect=False, command=self.changeMapping, - extraArgs=[action, lbl]) + extraArgs=[action]) btn.setTransparency(True) btn.reparentTo(item) return item -app = App() +app = MappingGUIDemo() app.run() diff --git a/samples/gamepad/models/xbone-icons.egg b/samples/gamepad/models/xbone-icons.egg new file mode 100644 index 0000000000..cc2fb417ad --- /dev/null +++ b/samples/gamepad/models/xbone-icons.egg @@ -0,0 +1,384 @@ + { Y-Up } + + xbone-icons { + xbone-icons.png + format { rgba } + alpha { dual } +} + + vpool { + 0 { + -0.5 0.5 0 + { 0.00390625 0.9921875 } + } + 1 { + -0.5 -0.5 0 + { 0.00390625 0.7578125 } + } + 2 { + 0.5 -0.5 0 + { 0.12109375 0.7578125 } + } + 3 { + 0.5 0.5 0 + { 0.12109375 0.9921875 } + } + 4 { + -0.5 0.5 0 + { 0.12890625 0.9921875 } + } + 5 { + -0.5 -0.5 0 + { 0.12890625 0.7578125 } + } + 6 { + 0.5 -0.5 0 + { 0.24609375 0.7578125 } + } + 7 { + 0.5 0.5 0 + { 0.24609375 0.9921875 } + } + 8 { + -0.5 0.5 0 + { 0.25390625 0.9921875 } + } + 9 { + -0.5 -0.5 0 + { 0.25390625 0.7578125 } + } + 10 { + 0.5 -0.5 0 + { 0.37109375 0.7578125 } + } + 11 { + 0.5 0.5 0 + { 0.37109375 0.9921875 } + } + 12 { + -0.5 0.5 0 + { 0.37890625 0.9921875 } + } + 13 { + -0.5 -0.5 0 + { 0.37890625 0.7578125 } + } + 14 { + 0.5 -0.5 0 + { 0.49609375 0.7578125 } + } + 15 { + 0.5 0.5 0 + { 0.49609375 0.9921875 } + } + 16 { + -0.5 0.5 0 + { 0.50390625 0.9921875 } + } + 17 { + -0.5 -0.5 0 + { 0.50390625 0.7578125 } + } + 18 { + 0.5 -0.5 0 + { 0.62109375 0.7578125 } + } + 19 { + 0.5 0.5 0 + { 0.62109375 0.9921875 } + } + 20 { + -0.5 0.5 0 + { 0.62890625 0.9921875 } + } + 21 { + -0.5 -0.5 0 + { 0.62890625 0.7578125 } + } + 22 { + 0.5 -0.5 0 + { 0.74609375 0.7578125 } + } + 23 { + 0.5 0.5 0 + { 0.74609375 0.9921875 } + } + 24 { + -0.5 0.5 0 + { 0.75390625 0.9921875 } + } + 25 { + -0.5 -0.5 0 + { 0.75390625 0.7578125 } + } + 26 { + 0.5 -0.5 0 + { 0.87109375 0.7578125 } + } + 27 { + 0.5 0.5 0 + { 0.87109375 0.9921875 } + } + 28 { + -0.5 0.5 0 + { 0.87890625 0.9921875 } + } + 29 { + -0.5 -0.5 0 + { 0.87890625 0.7578125 } + } + 30 { + 0.5 -0.5 0 + { 0.99609375 0.7578125 } + } + 31 { + 0.5 0.5 0 + { 0.99609375 0.9921875 } + } + 32 { + -0.5 0.5 0 + { 0.12890625 0.7421875 } + } + 33 { + -0.5 -0.5 0 + { 0.12890625 0.5078125 } + } + 34 { + 0.5 -0.5 0 + { 0.24609375 0.5078125 } + } + 35 { + 0.5 0.5 0 + { 0.24609375 0.7421875 } + } + 36 { + -0.5 0.5 0 + { 0.00390625 0.7421875 } + } + 37 { + -0.5 -0.5 0 + { 0.00390625 0.5078125 } + } + 38 { + 0.5 -0.5 0 + { 0.12109375 0.5078125 } + } + 39 { + 0.5 0.5 0 + { 0.12109375 0.7421875 } + } + 40 { + -0.5 0.5 0 + { 0.25390625 0.7421875 } + } + 41 { + -0.5 -0.5 0 + { 0.25390625 0.5078125 } + } + 42 { + 0.5 -0.5 0 + { 0.37109375 0.5078125 } + } + 43 { + 0.5 0.5 0 + { 0.37109375 0.7421875 } + } + 44 { + -0.5 0.5 0 + { 0.37890625 0.7421875 } + } + 45 { + -0.5 -0.5 0 + { 0.37890625 0.5078125 } + } + 46 { + 0.5 -0.5 0 + { 0.49609375 0.5078125 } + } + 47 { + 0.5 0.5 0 + { 0.49609375 0.7421875 } + } + 48 { + -0.5 0.5 0 + { 0.62890625 0.7421875 } + } + 49 { + -0.5 -0.5 0 + { 0.62890625 0.5078125 } + } + 50 { + 0.5 -0.5 0 + { 0.74609375 0.5078125 } + } + 51 { + 0.5 0.5 0 + { 0.74609375 0.7421875 } + } + 52 { + -0.5 0.5 0 + { 0.50390625 0.7421875 } + } + 53 { + -0.5 -0.5 0 + { 0.50390625 0.5078125 } + } + 54 { + 0.5 -0.5 0 + { 0.62109375 0.5078125 } + } + 55 { + 0.5 0.5 0 + { 0.62109375 0.7421875 } + } + 56 { + -0.5 0.5 0 + { 0.75390625 0.7421875 } + } + 57 { + -0.5 -0.5 0 + { 0.75390625 0.5078125 } + } + 58 { + 0.5 -0.5 0 + { 0.87109375 0.5078125 } + } + 59 { + 0.5 0.5 0 + { 0.87109375 0.7421875 } + } + 60 { + -0.5 0.5 0 + { 0.87890625 0.7421875 } + } + 61 { + -0.5 -0.5 0 + { 0.87890625 0.5078125 } + } + 62 { + 0.5 -0.5 0 + { 0.99609375 0.5078125 } + } + 63 { + 0.5 0.5 0 + { 0.99609375 0.7421875 } + } + 64 { + -0.5 0.5 0 + { 0.00390625 0.4921875 } + } + 65 { + -0.5 -0.5 0 + { 0.00390625 0.2578125 } + } + 66 { + 0.5 -0.5 0 + { 0.12109375 0.2578125 } + } + 67 { + 0.5 0.5 0 + { 0.12109375 0.4921875 } + } +} + face_a { + { + { xbone-icons } + { 0 1 2 3 { vpool } } + } +} + face_b { + { + { xbone-icons } + { 4 5 6 7 { vpool } } + } +} + dpad { + { + { xbone-icons } + { 8 9 10 11 { vpool } } + } +} + dpad_down { + { + { xbone-icons } + { 12 13 14 15 { vpool } } + } +} + dpad_left { + { + { xbone-icons } + { 16 17 18 19 { vpool } } + } +} + dpad_right { + { + { xbone-icons } + { 20 21 22 23 { vpool } } + } +} + dpad_up { + { + { xbone-icons } + { 24 25 26 27 { vpool } } + } +} + XboxOne_LB { + { + { xbone-icons } + { 28 29 30 31 { vpool } } + } +} + lstick { + { + { xbone-icons } + { 32 33 34 35 { vpool } } + } +} + ltrigger { + { + { xbone-icons } + { 36 37 38 39 { vpool } } + } +} + start { + { + { xbone-icons } + { 40 41 42 43 { vpool } } + } +} + rshoulder { + { + { xbone-icons } + { 44 45 46 47 { vpool } } + } +} + rstick { + { + { xbone-icons } + { 48 49 50 51 { vpool } } + } +} + rtrigger { + { + { xbone-icons } + { 52 53 54 55 { vpool } } + } +} + back { + { + { xbone-icons } + { 56 57 58 59 { vpool } } + } +} + face_x { + { + { xbone-icons } + { 60 61 62 63 { vpool } } + } +} + face_y { + { + { xbone-icons } + { 64 65 66 67 { vpool } } + } +} diff --git a/samples/gamepad/models/xbone-icons.png b/samples/gamepad/models/xbone-icons.png new file mode 100644 index 0000000000000000000000000000000000000000..e8fd66b487f8895846b3c2270cb6eca4be34fcca GIT binary patch literal 29275 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 Date: Sat, 29 Sep 2018 15:17:58 +0200 Subject: [PATCH 69/82] Updated steering wheel sample made steering wheel sample work with current version of input classes --- samples/gamepad/steeringWheel.py | 105 +++++++++++++++++++------------ 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py index 306419c378..65e5aa63f4 100644 --- a/samples/gamepad/steeringWheel.py +++ b/samples/gamepad/steeringWheel.py @@ -11,19 +11,21 @@ from direct.showbase.ShowBase import ShowBase from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 from direct.gui.OnscreenText import OnscreenText -loadPrcFileData("", "notify-level-device debug") +loadPrcFileData("", """ + default-fov 60 + notify-level-device debug +""") class App(ShowBase): def __init__(self): ShowBase.__init__(self) - # print all events sent through the messenger - self.messenger.toggleVerbose() + # Print all events sent through the messenger + #self.messenger.toggleVerbose() self.lblWarning = OnscreenText( text = "No devices found", fg=(1,0,0,1), scale = .25) - self.lblWarning.hide() self.lblAction = OnscreenText( text = "Action", @@ -31,7 +33,11 @@ class App(ShowBase): scale = .15) self.lblAction.hide() - self.checkDevices() + # Is there a steering wheel connected? + self.wheel = None + devices = self.devices.getDevices(InputDevice.DC_steering_wheel) + if devices: + self.connect(devices[0]) self.currentMoveSpeed = 0.0 self.maxAccleration = 28.0 @@ -40,17 +46,15 @@ class App(ShowBase): self.maxSpeed = 80.0 # Accept device dis-/connection events - # NOTE: catching the events here will overwrite the accept in showbase, hence - # we need to forward the event in the functions we set here! self.accept("connect-device", self.connect) self.accept("disconnect-device", self.disconnect) self.accept("escape", exit) - self.accept("flight_stick0-start", exit) # Accept button events of the first connected steering wheel - self.accept("steering_wheel0-action_a", self.doAction, extraArgs=[True, "Action"]) - self.accept("steering_wheel0-action_a-up", self.doAction, extraArgs=[False, "Release"]) + self.accept("steering_wheel0-face_a", self.action, extraArgs=["Action"]) + self.accept("steering_wheel0-face_a-up", self.actionUp) + self.accept("steering_wheel0-hat_up", self.center_wheel) self.environment = loader.loadModel("environment") self.environment.reparentTo(render) @@ -60,51 +64,74 @@ class App(ShowBase): # In real world applications, you should notice the user and give him enough time # to center the wheel until you store the center position of the controler! self.wheelCenter = 0 - wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) - if len(wheels) > 0: - self.wheelCenter = wheels[0].findControl(InputDevice.C_wheel).state + if self.wheel is not None: + self.wheelCenter = self.wheel.findAxis(InputDevice.Axis.wheel).value # disable pandas default mouse-camera controls so we can handle the camera # movements by ourself self.disableMouse() - base.camera.setZ(2) + self.reset() self.taskMgr.add(self.moveTask, "movement update task") def connect(self, device): - # we need to forward the event to the connectDevice function of showbase - self.connectDevice(device) - # Now we can check for ourself - self.checkDevices() + """Event handler that is called when a device is discovered.""" + + # We're only interested if this is a steering wheel and we don't have a + # wheel yet. + if device.device_class == InputDevice.DC_steering_wheel and not self.wheel: + print("Found %s" % (device)) + self.wheel = device + + # Enable this device to ShowBase so that we can receive events. + # We set up the events with a prefix of "steering_wheel0-". + self.attachInputDevice(device, prefix="steering_wheel0") + + # Hide the warning that we have no devices. + self.lblWarning.hide() def disconnect(self, device): - # we need to forward the event to the disconnectDevice function of showbase - self.disconnectDevice(device) - # Now we can check for ourself - self.checkDevices() + """Event handler that is called when a device is removed.""" - def checkDevices(self): - # check if we have wheel devices connected - if self.devices.get_devices(InputDevice.DC_steering_wheel): - # we have at least one steering wheel device - self.lblWarning.hide() + if self.wheel != device: + # We don't care since it's not our wheel. + return + + # Tell ShowBase that the device is no longer needed. + print("Disconnected %s" % (device)) + self.detachInputDevice(device) + self.wheel = None + + # Do we have any steering wheels? Attach the first other steering wheel. + devices = self.devices.getDevices(InputDevice.DC_steering_wheel) + if devices: + self.connect(devices[0]) else: - # no devices connected + # No devices. Show the warning. self.lblWarning.show() - def doAction(self, showText, text): - if showText and self.lblAction.isHidden(): - self.lblAction.show() - else: - self.lblAction.hide() + def reset(self): + """Reset the camera to the initial position.""" + self.camera.setPosHpr(0, -200, 2, 0, 0, 0) + + def action(self, button): + # Just show which button has been pressed. + self.lblAction.text = "Pressed %s" % button + self.lblAction.show() + + def actionUp(self): + # Hide the label showing which button is pressed. + self.lblAction.hide() + + def center_wheel(self): + """Reset the wheels center rotation to the current rotation of the wheel""" + self.wheelCenter = self.wheel.findAxis(InputDevice.Axis.wheel).value def moveTask(self, task): dt = globalClock.getDt() movementVec = Vec3() - wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) - if len(wheels) == 0: - # savety check + if not self.wheel: return task.cont if self.currentMoveSpeed > 0: @@ -114,21 +141,21 @@ class App(ShowBase): # we will use the first found wheel # Acclerate - acclearatorPedal = wheels[0].findControl(InputDevice.C_accelerator).state + accleratorPedal = self.wheel.findAxis(InputDevice.Axis.accelerator).value accleration = accleratorPedal * self.maxAccleration if self.currentMoveSpeed > accleratorPedal * self.maxSpeed: self.currentMoveSpeed -= dt * self.deaccleration self.currentMoveSpeed += dt * accleration # Break - breakPedal = wheels[0].findControl(InputDevice.C_brake).state + breakPedal = self.wheel.findAxis(InputDevice.Axis.brake).value deacleration = breakPedal * self.deaclerationBreak self.currentMoveSpeed -= dt * deacleration if self.currentMoveSpeed < 0: self.currentMoveSpeed = 0 # Steering - rotation = self.wheelCenter - wheels[0].findControl(InputDevice.C_wheel).state + rotation = self.wheelCenter - self.wheel.findAxis(InputDevice.Axis.wheel).value base.camera.setH(base.camera, 100 * dt * rotation) # calculate movement From d764574d071bd40c9b35f3083582303354cd889f Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Sat, 29 Sep 2018 15:23:54 +0200 Subject: [PATCH 70/82] Added updated flight stick sample --- samples/gamepad/flightstick.py | 168 +++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 samples/gamepad/flightstick.py diff --git a/samples/gamepad/flightstick.py b/samples/gamepad/flightstick.py new file mode 100644 index 0000000000..1c4504330c --- /dev/null +++ b/samples/gamepad/flightstick.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +''' +Demonstrate usage of steering wheels + +In this sample you can use a wheel type device to control the camera and +show some messages on screen. You can acclerate forward using the +accleration pedal and slow down using the break pedal. +''' + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 +from direct.gui.OnscreenText import OnscreenText + +loadPrcFileData("", """ + default-fov 60 + notify-level-device debug +""") + +class App(ShowBase): + def __init__(self): + ShowBase.__init__(self) + # Print all events sent through the messenger + #self.messenger.toggleVerbose() + + self.lblWarning = OnscreenText( + text = "No devices found", + fg=(1,0,0,1), + scale = .25) + + self.lblAction = OnscreenText( + text = "Action", + fg=(1,1,1,1), + scale = .15) + self.lblAction.hide() + + # Is there a gamepad connected? + self.flightStick = None + devices = self.devices.getDevices(InputDevice.DC_flight_stick) + if devices: + self.connect(devices[0]) + + self.currentMoveSpeed = 0.0 + self.maxAccleration = 28.0 + self.deaccleration = 10.0 + self.deaclerationBreak = 37.0 + self.maxSpeed = 80.0 + + # Accept device dis-/connection events + self.accept("connect-device", self.connect) + self.accept("disconnect-device", self.disconnect) + + self.accept("escape", exit) + self.accept("flight_stick0-start", exit) + + # Accept button events of the first connected flight stick + self.accept("flight_stick0-action_a", self.action, extraArgs=["Action"]) + self.accept("flight_stick0-action_a-up", self.actionUp) + + self.environment = loader.loadModel("environment") + self.environment.reparentTo(render) + + # save the center position of the stick + # NOTE: here we assume, that the stick is centered when the application get started. + # In real world applications, you should notice the user and give him enough time + # to center the wheel until you store the center position of the controler! + self.hcenter = 0 + self.xcenter = 0 + self.ycenter = 0 + if self.flightStick: + self.hcenter = self.flightStick.findAxis(InputDevice.Axis.pitch).value + self.xcenter = self.flightStick.findAxis(InputDevice.Axis.x).value + self.ycenter = self.flightStick.findAxis(InputDevice.Axis.y).value + + # disable pandas default mouse-camera controls so we can handle the camera + # movements by ourself + self.disableMouse() + self.reset() + + self.taskMgr.add(self.moveTask, "movement update task") + + def connect(self, device): + """Event handler that is called when a device is discovered.""" + + # We're only interested if this is a flight stick and we don't have a + # flight stick yet. + if device.device_class == InputDevice.DC_flight_stick and not self.flightStick: + print("Found %s" % (device)) + self.flightStick = device + + # Enable this device to ShowBase so that we can receive events. + # We set up the events with a prefix of "flight_stick0-". + self.attachInputDevice(device, prefix="flight_stick0") + + # Hide the warning that we have no devices. + self.lblWarning.hide() + + def disconnect(self, device): + """Event handler that is called when a device is removed.""" + + if self.flightStick != device: + # We don't care since it's not our gamepad. + return + + # Tell ShowBase that the device is no longer needed. + print("Disconnected %s" % (device)) + self.detachInputDevice(device) + self.flightStick = None + + # Do we have any other gamepads? Attach the first other gamepad. + devices = self.devices.getDevices(InputDevice.DC_flight_stick) + if devices: + self.connect(devices[0]) + else: + # No devices. Show the warning. + self.lblWarning.show() + + def reset(self): + """Reset the camera to the initial position.""" + + self.camera.setPosHpr(0, -200, 10, 0, 0, 0) + + def action(self, button): + # Just show which button has been pressed. + self.lblAction.text = "Pressed %s" % button + self.lblAction.show() + + def actionUp(self): + # Hide the label showing which button is pressed. + self.lblAction.hide() + + def moveTask(self, task): + dt = globalClock.getDt() + + if not self.flightStick: + return task.cont + + if self.currentMoveSpeed > 0: + self.currentMoveSpeed -= dt * self.deaccleration + if self.currentMoveSpeed < 0: + self.currentMoveSpeed = 0 + + # we will use the first found wheel + # Acclerate + throttle = self.flightStick.findAxis(InputDevice.Axis.throttle).value + accleration = throttle * self.maxAccleration + if self.currentMoveSpeed > throttle * self.maxSpeed: + self.currentMoveSpeed -= dt * self.deaccleration + self.currentMoveSpeed += dt * accleration + + # Steering + + # Control the cameras yaw/Headding + stick_yaw = self.flightStick.findAxis(InputDevice.Axis.yaw) + base.camera.setH(base.camera, 100 * dt * (stick_yaw.value - self.hcenter)) + # Control the cameras pitch + stick_y = self.flightStick.findAxis(InputDevice.Axis.pitch) + base.camera.setP(base.camera, 100 * dt * (stick_y.value - self.ycenter)) + # Control the cameras roll + stick_X = self.flightStick.findAxis(InputDevice.Axis.roll) + base.camera.setR(base.camera, 100 * dt * (stick_X.value - self.xcenter)) + + # calculate movement + base.camera.setY(base.camera, dt * self.currentMoveSpeed) + + return task.cont + +app = App() +app.run() From 3d66e2290557fffb6c2807da78ecccc2f69f829d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:47:59 +0200 Subject: [PATCH 71/82] samples: improvements to flight stick sample program --- samples/gamepad/flightstick.py | 49 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/samples/gamepad/flightstick.py b/samples/gamepad/flightstick.py index 1c4504330c..0a06c52585 100644 --- a/samples/gamepad/flightstick.py +++ b/samples/gamepad/flightstick.py @@ -1,10 +1,9 @@ #!/usr/bin/env python ''' -Demonstrate usage of steering wheels +Demonstrate usage of flight stick -In this sample you can use a wheel type device to control the camera and -show some messages on screen. You can acclerate forward using the -accleration pedal and slow down using the break pedal. +In this sample you can use a flight stick to control the camera and show some +messages on screen. You can accelerate using the throttle. ''' from direct.showbase.ShowBase import ShowBase @@ -16,6 +15,10 @@ loadPrcFileData("", """ notify-level-device debug """) +STICK_DEAD_ZONE = 0.02 +THROTTLE_DEAD_ZONE = 0.02 + + class App(ShowBase): def __init__(self): ShowBase.__init__(self) @@ -53,24 +56,12 @@ class App(ShowBase): self.accept("flight_stick0-start", exit) # Accept button events of the first connected flight stick - self.accept("flight_stick0-action_a", self.action, extraArgs=["Action"]) - self.accept("flight_stick0-action_a-up", self.actionUp) + self.accept("flight_stick0-trigger", self.action, extraArgs=["Trigger"]) + self.accept("flight_stick0-trigger-up", self.actionUp) self.environment = loader.loadModel("environment") self.environment.reparentTo(render) - # save the center position of the stick - # NOTE: here we assume, that the stick is centered when the application get started. - # In real world applications, you should notice the user and give him enough time - # to center the wheel until you store the center position of the controler! - self.hcenter = 0 - self.xcenter = 0 - self.ycenter = 0 - if self.flightStick: - self.hcenter = self.flightStick.findAxis(InputDevice.Axis.pitch).value - self.xcenter = self.flightStick.findAxis(InputDevice.Axis.x).value - self.ycenter = self.flightStick.findAxis(InputDevice.Axis.y).value - # disable pandas default mouse-camera controls so we can handle the camera # movements by ourself self.disableMouse() @@ -139,9 +130,10 @@ class App(ShowBase): if self.currentMoveSpeed < 0: self.currentMoveSpeed = 0 - # we will use the first found wheel - # Acclerate + # Accelerate using the throttle. Apply deadzone of 0.01. throttle = self.flightStick.findAxis(InputDevice.Axis.throttle).value + if abs(throttle) < THROTTLE_DEAD_ZONE: + throttle = 0 accleration = throttle * self.maxAccleration if self.currentMoveSpeed > throttle * self.maxSpeed: self.currentMoveSpeed -= dt * self.deaccleration @@ -151,17 +143,26 @@ class App(ShowBase): # Control the cameras yaw/Headding stick_yaw = self.flightStick.findAxis(InputDevice.Axis.yaw) - base.camera.setH(base.camera, 100 * dt * (stick_yaw.value - self.hcenter)) + if abs(stick_yaw.value) > STICK_DEAD_ZONE: + base.camera.setH(base.camera, 100 * dt * stick_yaw.value) + # Control the cameras pitch stick_y = self.flightStick.findAxis(InputDevice.Axis.pitch) - base.camera.setP(base.camera, 100 * dt * (stick_y.value - self.ycenter)) + if abs(stick_y.value) > STICK_DEAD_ZONE: + base.camera.setP(base.camera, 100 * dt * stick_y.value) + # Control the cameras roll - stick_X = self.flightStick.findAxis(InputDevice.Axis.roll) - base.camera.setR(base.camera, 100 * dt * (stick_X.value - self.xcenter)) + stick_x = self.flightStick.findAxis(InputDevice.Axis.roll) + if abs(stick_x.value) > STICK_DEAD_ZONE: + base.camera.setR(base.camera, 100 * dt * stick_x.value) # calculate movement base.camera.setY(base.camera, dt * self.currentMoveSpeed) + # Make sure camera does not go below the ground. + if base.camera.getZ() < 1: + base.camera.setZ(1) + return task.cont app = App() From f518b9aaf1acfea4a311b66e0335fac3f44d4c12 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:48:39 +0200 Subject: [PATCH 72/82] device: fixes to sporadic device class detection on Linux --- panda/src/device/evdevInputDevice.cxx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 4fdd451cd5..3a9a394eec 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -239,8 +239,7 @@ init_device() { LightMutexHolder holder(_lock); - uint8_t evtypes[(EV_MAX + 8) >> 3]; - memset(evtypes, 0, sizeof(evtypes)); + uint8_t evtypes[(EV_MAX + 8) >> 3] = {0}; char name[128]; if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || ioctl(_fd, EVIOCGBIT(0, sizeof(evtypes)), evtypes) < 0) { @@ -267,10 +266,9 @@ init_device() { bool has_keys = false; bool has_axes = false; - uint8_t keys[(KEY_MAX + 8) >> 3]; + uint8_t keys[(KEY_MAX + 8) >> 3] = {0}; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. - memset(keys, 0, sizeof(keys)); ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys); has_keys = true; @@ -280,10 +278,9 @@ init_device() { } int num_bits = 0; - uint8_t axes[(ABS_MAX + 8) >> 3]; + uint8_t axes[(ABS_MAX + 8) >> 3] = {0}; if (test_bit(EV_ABS, evtypes)) { // Check which axes are on the device. - memset(axes, 0, sizeof(axes)); num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; has_axes = true; } @@ -302,8 +299,7 @@ init_device() { // Try to detect which type of device we have here if (_device_class == DC_unknown) { - int device_scores[DC_COUNT]; - memset(device_scores, 0, sizeof(device_scores)); + int device_scores[DC_COUNT] = {0}; // Test for specific keys if (test_bit(BTN_GAMEPAD, keys) && test_bit(ABS_X, axes) && test_bit(ABS_RX, axes)) { @@ -374,8 +370,7 @@ init_device() { if (has_keys) { // Also check whether the buttons are currently pressed. - uint8_t states[(KEY_MAX + 8) >> 3]; - memset(states, 0, sizeof(states)); + uint8_t states[(KEY_MAX + 8) >> 3] = {0}; ioctl(_fd, EVIOCGKEY(sizeof(states)), states); for (int i = 0; i <= KEY_MAX; ++i) { @@ -565,8 +560,7 @@ init_device() { } if (test_bit(EV_FF, evtypes)) { - uint8_t effects[(FF_MAX + 8) >> 3]; - memset(effects, 0, sizeof(effects)); + uint8_t effects[(FF_MAX + 8) >> 3] = {0}; ioctl(_fd, EVIOCGBIT(EV_FF, sizeof(effects)), effects); if (test_bit(FF_RUMBLE, effects)) { From 4b77812b3528c1b12b795c8fb84b60cb29ab783c Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:49:13 +0200 Subject: [PATCH 73/82] device: fix inverted flight stick throttle on Linux --- panda/src/device/evdevInputDevice.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 3a9a394eec..4cdf52a880 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -540,7 +540,7 @@ init_device() { std::swap(absinfo.maximum, absinfo.minimum); } if (axis == Axis::throttle && (quirks & QB_centered_throttle) != 0) { - index = add_axis(axis, absinfo.maximum, absinfo.minimum, true); + index = add_axis(axis, absinfo.minimum, absinfo.maximum, true); } else { index = add_axis(axis, absinfo.minimum, absinfo.maximum); } From 9618cd219368e0335d233ae8b8a64c7cc42ce18f Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 21:06:24 +0200 Subject: [PATCH 74/82] device: ensure libputil is initialized before scanning devices --- panda/src/device/inputDeviceManager.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 140b4379f1..4bc69963eb 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -33,6 +33,8 @@ InputDeviceManager() : _lock("InputDeviceManager") { */ void InputDeviceManager:: make_global_ptr() { + init_libputil(); + #ifdef _WIN32 _global_ptr = new WinInputDeviceManager; #elif defined(__APPLE__) From ada3fa5513ee5496fb1ff280c73d5c7cfd911269 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 8 Oct 2018 01:44:17 +0200 Subject: [PATCH 75/82] Compile fixes on input-overhaul branch --- panda/src/device/evdevInputDevice.cxx | 1 - panda/src/device/inputDeviceManager.cxx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 4cdf52a880..6fda488b0f 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -673,7 +673,6 @@ process_events() { int y = _pointer_data.get_y(); bool have_pointer = false; double time = ClockObject::get_global_clock()->get_frame_time(); - ButtonHandle button; int index; // It seems that some devices send a single EV_SYN event when being diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 4bc69963eb..8cd470e271 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -16,6 +16,7 @@ #include "linuxInputDeviceManager.h" #include "winInputDeviceManager.h" #include "throw_event.h" +#include "config_putil.h" InputDeviceManager *InputDeviceManager::_global_ptr = nullptr; From 37d2ff64ded0786d07ea22a2c15fbe460dc8e1e5 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 25 Nov 2018 23:38:07 +0100 Subject: [PATCH 76/82] putil: add id and pressure fields to PointerData --- panda/src/putil/pointerData.I | 54 ++++++++++++++--------------------- panda/src/putil/pointerData.h | 19 +++++++----- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/panda/src/putil/pointerData.I b/panda/src/putil/pointerData.I index 6697c31d1a..a23b1c464b 100644 --- a/panda/src/putil/pointerData.I +++ b/panda/src/putil/pointerData.I @@ -11,37 +11,6 @@ * @date 2002-07-15 */ -/** - * - */ -INLINE PointerData:: -PointerData() { - _in_window = false; - _xpos = 0; - _ypos = 0; -} - -/** - * - */ -INLINE PointerData:: -PointerData(const PointerData ©) : - _in_window(copy._in_window), - _xpos(copy._xpos), - _ypos(copy._ypos) -{ -} - -/** - * - */ -INLINE void PointerData:: -operator = (const PointerData ©) { - _in_window = copy._in_window; - _xpos = copy._xpos; - _ypos = copy._ypos; -} - /** * */ @@ -59,13 +28,34 @@ get_y() const { } /** - * + * If this returns false, the pointer is not currently present in the window + * and the values returned by get_x() and get_y() may not be meaningful. */ INLINE bool PointerData:: get_in_window() const { return _in_window; } +/** + * Returns a unique identifier for this pointer. This is for tracking + * individual fingers. This value should not be assumed to have a specific + * meaning other than that there will not be two different pointers active + * simultaneously with the same identifier. + */ +INLINE int PointerData:: +get_id() const { + return _id; +} + +/** + * Returns the pressure of the pointer. For mice, this will be 1.0 if any + * button is pressed, 0.0 otherwise. + */ +INLINE double PointerData:: +get_pressure() const { + return _pressure; +} + INLINE std::ostream &operator << (std::ostream &out, const PointerData &md) { md.output(out); diff --git a/panda/src/putil/pointerData.h b/panda/src/putil/pointerData.h index aa3f3aeb62..ab0aee374e 100644 --- a/panda/src/putil/pointerData.h +++ b/panda/src/putil/pointerData.h @@ -24,24 +24,29 @@ */ class EXPCL_PANDA_PUTIL PointerData { PUBLISHED: - INLINE PointerData(); - INLINE PointerData(const PointerData ©); - INLINE void operator = (const PointerData ©); - INLINE double get_x() const; INLINE double get_y() const; INLINE bool get_in_window() const; +public: + INLINE int get_id() const; + INLINE double get_pressure() const; + void output(std::ostream &out) const; +PUBLISHED: MAKE_PROPERTY(x, get_x); MAKE_PROPERTY(y, get_y); + MAKE_PROPERTY(id, get_id); MAKE_PROPERTY(in_window, get_in_window); + MAKE_PROPERTY(pressure, get_pressure); public: - bool _in_window; - double _xpos; - double _ypos; + bool _in_window = false; + double _xpos = 0.0; + double _ypos = 0.0; + double _pressure = 0.0; + int _id = 0; }; INLINE std::ostream &operator << (std::ostream &out, const PointerData &md); From cc08bacec514d87038c37a8f7da0788fd722e7be Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 17:13:35 +0100 Subject: [PATCH 77/82] cocoa: fix compilation error --- panda/src/cocoadisplay/cocoaGraphicsWindow.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index 9801fe4341..df3ecf3b12 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -102,7 +102,7 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); _input_devices.push_back(device.p()); - _input = move(device); + _input = std::move(device); CocoaGraphicsPipe *cocoa_pipe; DCAST_INTO_V(cocoa_pipe, _pipe); From 2087d8ee84ee8cc051173787a81836c4a79a2295 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 17:25:23 +0100 Subject: [PATCH 78/82] device: change InputDevice enums to enum classes --- panda/src/device/clientDevice.cxx | 4 +- panda/src/device/clientDevice.h | 2 +- panda/src/device/clientTrackerDevice.I | 3 +- panda/src/device/evdevInputDevice.cxx | 94 +++++++++--------- panda/src/device/evdevInputDevice.h | 2 +- panda/src/device/inputDevice.I | 45 +++++---- panda/src/device/inputDevice.cxx | 51 ++++------ panda/src/device/inputDevice.h | 95 ++++++++++--------- panda/src/device/ioKitInputDevice.cxx | 48 +++++----- panda/src/device/linuxJoystickDevice.cxx | 20 ++-- panda/src/device/winRawInputDevice.cxx | 44 ++++----- panda/src/device/xInputDevice.cxx | 12 +-- panda/src/display/graphicsWindowInputDevice.I | 7 -- .../src/display/graphicsWindowInputDevice.cxx | 17 ++-- panda/src/display/graphicsWindowInputDevice.h | 5 +- 15 files changed, 223 insertions(+), 226 deletions(-) diff --git a/panda/src/device/clientDevice.cxx b/panda/src/device/clientDevice.cxx index 21a3a4d926..278977f30b 100644 --- a/panda/src/device/clientDevice.cxx +++ b/panda/src/device/clientDevice.cxx @@ -23,8 +23,8 @@ TypeHandle ClientDevice::_type_handle; */ ClientDevice:: ClientDevice(ClientBase *client, TypeHandle device_type, - const std::string &device_name, int device_flags) : - InputDevice(device_name, DC_unknown, device_flags), + const std::string &device_name) : + InputDevice(device_name, DeviceClass::unknown), _client(client), _device_type(device_type) { diff --git a/panda/src/device/clientDevice.h b/panda/src/device/clientDevice.h index e9ac1b30d3..b77d707484 100644 --- a/panda/src/device/clientDevice.h +++ b/panda/src/device/clientDevice.h @@ -27,7 +27,7 @@ class ClientBase; class EXPCL_PANDA_DEVICE ClientDevice : public InputDevice { protected: ClientDevice(ClientBase *client, TypeHandle device_type, - const std::string &device_name, int device_flags=0); + const std::string &device_name); public: virtual ~ClientDevice(); diff --git a/panda/src/device/clientTrackerDevice.I b/panda/src/device/clientTrackerDevice.I index 19c0596394..6ff99528e5 100644 --- a/panda/src/device/clientTrackerDevice.I +++ b/panda/src/device/clientTrackerDevice.I @@ -16,6 +16,7 @@ */ INLINE ClientTrackerDevice:: ClientTrackerDevice(ClientBase *client, const std::string &device_name): - ClientDevice(client, get_class_type(), device_name, IDF_has_tracker) + ClientDevice(client, get_class_type(), device_name) { + enable_feature(Feature::tracker); } diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 6fda488b0f..7c0dad8c82 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -66,27 +66,27 @@ static const struct DeviceMapping { int quirks; } mapping_presets[] = { // NVIDIA Shield Controller - {0x0955, 0x7214, InputDevice::DC_gamepad, QB_rstick_from_z}, + {0x0955, 0x7214, InputDevice::DeviceClass::gamepad, QB_rstick_from_z}, // T.Flight Hotas X - {0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle | QB_rudder_from_throttle}, + {0x044f, 0xb108, InputDevice::DeviceClass::flight_stick, QB_centered_throttle | QB_reversed_throttle | QB_rudder_from_throttle}, // Xbox 360 Wireless Controller - {0x045e, 0x0719, InputDevice::DC_gamepad, QB_connect_if_nonzero}, + {0x045e, 0x0719, InputDevice::DeviceClass::gamepad, QB_connect_if_nonzero}, // Jess Tech Colour Rumble Pad - {0x0f30, 0x0111, InputDevice::DC_gamepad, 0}, + {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0}, // 3Dconnexion Space Traveller 3D Mouse - {0x046d, 0xc623, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Pilot 3D Mouse - {0x046d, 0xc625, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc625, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Navigator 3D Mouse - {0x046d, 0xc626, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc626, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Explorer 3D Mouse - {0x046d, 0xc627, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc627, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Navigator for Notebooks - {0x046d, 0xc628, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc628, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion SpacePilot Pro 3D Mouse - {0x046d, 0xc629, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc629, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Mouse Pro - {0x046d, 0xc62b, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc62b, InputDevice::DeviceClass::spatial_mouse, 0}, {0}, }; @@ -273,7 +273,7 @@ init_device() { has_keys = true; if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) { - _flags |= IDF_has_keyboard; + enable_feature(Feature::keyboard); } } @@ -298,38 +298,38 @@ init_device() { } // Try to detect which type of device we have here - if (_device_class == DC_unknown) { - int device_scores[DC_COUNT] = {0}; + if (_device_class == DeviceClass::unknown) { + int device_scores[(size_t)DeviceClass::spatial_mouse] = {0}; // Test for specific keys if (test_bit(BTN_GAMEPAD, keys) && test_bit(ABS_X, axes) && test_bit(ABS_RX, axes)) { - device_scores[DC_gamepad] += 5; - device_scores[DC_steering_wheel] += 5; - device_scores[DC_flight_stick] += 5; + device_scores[(size_t)DeviceClass::gamepad] += 5; + device_scores[(size_t)DeviceClass::steering_wheel] += 5; + device_scores[(size_t)DeviceClass::flight_stick] += 5; } if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) { - device_scores[DC_steering_wheel] += 10; + device_scores[(size_t)DeviceClass::steering_wheel] += 10; } if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) { - device_scores[DC_steering_wheel] += 10; + device_scores[(size_t)DeviceClass::steering_wheel] += 10; } if (test_bit(BTN_JOYSTICK, keys) && test_bit(ABS_X, axes)) { - device_scores[DC_flight_stick] += 10; + device_scores[(size_t)DeviceClass::flight_stick] += 10; } if (test_bit(BTN_MOUSE, keys) && test_bit(EV_REL, evtypes)) { - device_scores[DC_mouse] += 20; + device_scores[(size_t)DeviceClass::mouse] += 20; } uint8_t unknown_keys[] = {KEY_POWER}; for (int i = 0; i < 1; i++) { if (test_bit(unknown_keys[i], keys)) { if (unknown_keys[i] == KEY_POWER) { } - device_scores[DC_unknown] += 20; + device_scores[(size_t)DeviceClass::unknown] += 20; } } - if (_flags & IDF_has_keyboard) { - device_scores[DC_keyboard] += 20; + if (_features & (unsigned int)Feature::keyboard) { + device_scores[(size_t)DeviceClass::keyboard] += 20; } // Test for specific name tags @@ -338,28 +338,28 @@ init_device() { lowercase_name[x] = tolower(lowercase_name[x]); } if (lowercase_name.find("gamepad") != string::npos) { - device_scores[DC_gamepad] += 10; + device_scores[(size_t)DeviceClass::gamepad] += 10; } if (lowercase_name.find("wheel") != string::npos) { - device_scores[DC_steering_wheel] += 10; + device_scores[(size_t)DeviceClass::steering_wheel] += 10; } if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) { - device_scores[DC_mouse] += 10; + device_scores[(size_t)DeviceClass::mouse] += 10; } if (lowercase_name.find("keyboard") != string::npos) { - device_scores[DC_keyboard] += 10; + device_scores[(size_t)DeviceClass::keyboard] += 10; } // List of lowercase names that occur in unknown devices string unknown_names[] = {"video bus", "power button", "sleep button"}; for(int i = 0; i < 3; i++) { if (lowercase_name.find(unknown_names[i]) != string::npos) { - device_scores[DC_unknown] += 20; + device_scores[(size_t)DeviceClass::unknown] += 20; } } // Check which device type got the most points int highest_score = 0; - for (size_t i = 0; i < DC_COUNT; i++) { + for (size_t i = 0; i < (size_t)DeviceClass::spatial_mouse; i++) { if (device_scores[i] > highest_score) { highest_score = device_scores[i]; _device_class = (DeviceClass)i; @@ -417,18 +417,18 @@ init_device() { Axis axis = Axis::none; switch (i) { case ABS_X: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_x; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = InputDevice::Axis::roll; } else { axis = InputDevice::Axis::x; } break; case ABS_Y: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_y; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = InputDevice::Axis::pitch; } else { axis = InputDevice::Axis::y; @@ -437,24 +437,24 @@ init_device() { case ABS_Z: if (quirks & QB_rstick_from_z) { axis = InputDevice::Axis::right_x; - } else if (_device_class == DC_gamepad) { + } else if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_trigger; have_analog_triggers = true; - } else if (_device_class == DC_3d_mouse) { + } else if (_device_class == DeviceClass::spatial_mouse) { axis = InputDevice::Axis::z; } else { axis = InputDevice::Axis::throttle; } break; case ABS_RX: - if (_device_class == DC_3d_mouse) { + if (_device_class == DeviceClass::spatial_mouse) { axis = InputDevice::Axis::pitch; } else if ((quirks & QB_rstick_from_z) == 0) { axis = InputDevice::Axis::right_x; } break; case ABS_RY: - if (_device_class == DC_3d_mouse) { + if (_device_class == DeviceClass::spatial_mouse) { axis = InputDevice::Axis::roll; } else if ((quirks & QB_rstick_from_z) == 0) { axis = InputDevice::Axis::right_y; @@ -463,7 +463,7 @@ init_device() { case ABS_RZ: if (quirks & QB_rstick_from_z) { axis = InputDevice::Axis::right_y; - } else if (_device_class == DC_gamepad) { + } else if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::right_trigger; have_analog_triggers = true; } else { @@ -484,7 +484,7 @@ init_device() { axis = InputDevice::Axis::wheel; break; case ABS_GAS: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::right_trigger; have_analog_triggers = true; } else { @@ -492,7 +492,7 @@ init_device() { } break; case ABS_BRAKE: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_trigger; have_analog_triggers = true; } else { @@ -503,7 +503,7 @@ init_device() { if (emulate_dpad) { _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { _buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_right())); } else { @@ -516,7 +516,7 @@ init_device() { if (emulate_dpad) { _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); } else { @@ -536,7 +536,7 @@ init_device() { // Also T.Flight Hotas X throttle is reversed and can go backwards. if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || (axis == Axis::throttle && (quirks & QB_reversed_throttle) != 0) || - (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { + (_device_class == DeviceClass::spatial_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { std::swap(absinfo.maximum, absinfo.minimum); } if (axis == Axis::throttle && (quirks & QB_centered_throttle) != 0) { @@ -556,7 +556,7 @@ init_device() { } if (test_bit(EV_REL, evtypes)) { - _flags |= IDF_has_pointer; + enable_feature(Feature::pointer); } if (test_bit(EV_FF, evtypes)) { @@ -565,7 +565,7 @@ init_device() { if (test_bit(FF_RUMBLE, effects)) { if (_can_write) { - _flags |= IDF_has_vibration; + enable_feature(Feature::vibration); } else { // Let the user know what he's missing out on. device_cat.warning() @@ -896,7 +896,7 @@ map_button(int code, DeviceClass device_class) { } } else if ((code & 0xfff0) == BTN_JOYSTICK) { - if (device_class == DC_gamepad) { + if (device_class == DeviceClass::gamepad) { // Based on "Jess Tech Colour Rumble Pad" static const ButtonHandle mapping[] = { GamepadButton::face_x(), diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 239ba78616..53c495ad08 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -64,7 +64,7 @@ private: int _rtrigger_code; public: - static ButtonHandle map_button(int code, DeviceClass device_class = DC_unknown); + static ButtonHandle map_button(int code, DeviceClass device_class = DeviceClass::unknown); public: static TypeHandle get_class_type() { diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index bcddc5cf52..aa8e069955 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -17,15 +17,7 @@ * */ INLINE InputDevice:: -InputDevice() : - _flags(0), - _device_class(DC_unknown), - _vendor_id(0), - _product_id(0), - _is_connected(false), - _event_sequence(0), - _enable_pointer_events(false) -{ +InputDevice() { _button_events = new ButtonEventList; } @@ -90,7 +82,7 @@ is_connected() const { /** * Returns an identification of the general type of device. If this could not - * be determined, returns DC_unknown. + * be determined, returns DeviceClass.unknown. */ INLINE InputDevice::DeviceClass InputDevice:: get_device_class() const { @@ -98,13 +90,21 @@ get_device_class() const { return _device_class; } +/** + * Returns true if the device supports the indicated feature. + */ +INLINE bool InputDevice:: +has_feature(Feature feature) const { + LightMutexHolder holder(_lock); + return (_features & (1 << (unsigned int)feature)) != 0; +} + /** * Returns true if this is a pointing device. */ INLINE bool InputDevice:: has_pointer() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_pointer) != 0); + return has_feature(Feature::pointer); } /** @@ -112,8 +112,7 @@ has_pointer() const { */ INLINE bool InputDevice:: has_keyboard() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_keyboard) != 0); + return has_feature(Feature::keyboard); } /** @@ -122,8 +121,7 @@ has_keyboard() const { */ INLINE bool InputDevice:: has_tracker() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_tracker) != 0); + return has_feature(Feature::tracker); } /** @@ -132,8 +130,7 @@ has_tracker() const { */ INLINE bool InputDevice:: has_vibration() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_vibration) != 0); + return has_feature(Feature::vibration); } /** @@ -142,8 +139,7 @@ has_vibration() const { */ INLINE bool InputDevice:: has_battery() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_battery) != 0); + return has_feature(Feature::battery); } /** @@ -393,6 +389,15 @@ disable_pointer_events() { _pointer_events.clear(); } +/** + * Called to indicate that the device supports the given feature. + * Assumes the lock is held. + */ +INLINE void InputDevice:: +enable_feature(Feature feature) { + _features |= (1 << (unsigned int)feature); +} + /** * Called to indicate that the device has been disconnected or connected from * its host. diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 742c31b688..fc754d0d97 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -23,16 +23,10 @@ TypeHandle InputDevice::_type_handle; * Defines a new InputDevice. */ InputDevice:: -InputDevice(const std::string &name, DeviceClass dev_class, int flags) : +InputDevice(const std::string &name, DeviceClass dev_class) : _name(name), - _flags(flags), _device_class(dev_class), - _vendor_id(0), - _product_id(0), - _is_connected(true), - _event_sequence(0), - _enable_pointer_events(false), - _lock("InputDevice") + _is_connected(true) { _button_events = new ButtonEventList; } @@ -346,7 +340,7 @@ output(std::ostream &out) const { out << "connected)"; - if (_device_class != DC_unknown) { + if (_device_class != DeviceClass::unknown) { out << ", " << _device_class; } @@ -362,19 +356,19 @@ output(std::ostream &out) const { << (_axes.size() != 1 ? 'e' : 'i') << 's'; } - if (_flags & IDF_has_pointer) { + if (_features & (unsigned int)Feature::pointer) { out << ", pointer"; } - if (_flags & IDF_has_keyboard) { + if (_features & (unsigned int)Feature::keyboard) { out << ", keyboard"; } - if (_flags & IDF_has_tracker) { + if (_features & (unsigned int)Feature::tracker) { out << ", tracker"; } - if (_flags & IDF_has_vibration) { + if (_features & (unsigned int)Feature::vibration) { out << ", vibration"; } - if (_flags & IDF_has_battery) { + if (_features & (unsigned int)Feature::battery) { out << ", battery"; if (_battery_data.level > 0 && _battery_data.max_level > 0) { @@ -504,41 +498,38 @@ do_poll() { std::string InputDevice:: format_device_class(DeviceClass dc) { switch (dc) { - case InputDevice::DC_unknown: + case InputDevice::DeviceClass::unknown: return "unknown"; - case InputDevice::DC_virtual: - return "virtual"; + case InputDevice::DeviceClass::virtual_device: + return "virtual_device"; - case InputDevice::DC_keyboard: + case InputDevice::DeviceClass::keyboard: return "keyboard"; - case InputDevice::DC_mouse: + case InputDevice::DeviceClass::mouse: return "mouse"; - case InputDevice::DC_touch: + case InputDevice::DeviceClass::touch: return "touch"; - case InputDevice::DC_gamepad: + case InputDevice::DeviceClass::gamepad: return "gamepad"; - case InputDevice::DC_flight_stick: + case InputDevice::DeviceClass::flight_stick: return "flight_stick"; - case InputDevice::DC_steering_wheel: + case InputDevice::DeviceClass::steering_wheel: return "steering_wheel"; - case InputDevice::DC_dance_pad: + case InputDevice::DeviceClass::dance_pad: return "dance_pad"; - case InputDevice::DC_hmd: + case InputDevice::DeviceClass::hmd: return "hmd"; - case InputDevice::DC_3d_mouse: - return "3d_mouse"; - - case InputDevice::DC_COUNT: - break; + case InputDevice::DeviceClass::spatial_mouse: + return "spatial_mouse"; } return "**invalid**"; } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 24b27186e3..0088ea8f86 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -52,35 +52,49 @@ class EXPCL_PANDA_DEVICE InputDevice : public TypedReferenceCount { PUBLISHED: // This enum contains information that can be used to identify the // type of input device. - enum DeviceClass { + enum class DeviceClass { // It is not known what type of device this is. - DC_unknown, + unknown, // This means that the device doesn't correspond to a physical // device, but rather to a dynamic source of input events. - DC_virtual, + virtual_device, // A physical, alphabetical keyboard. - DC_keyboard, + keyboard, - DC_mouse, - DC_touch, + mouse, + touch, // A gamepad with action buttons, a D-pad, and thumbsticks. - DC_gamepad, + gamepad, - DC_flight_stick, - DC_steering_wheel, - DC_dance_pad, + flight_stick, + steering_wheel, + dance_pad, // Head-mounted display. - DC_hmd, + hmd, // 3D mouse, such as produced by 3Dconnexion. - DC_3d_mouse, + spatial_mouse, + }; - // Count of this enum, used for loops - DC_COUNT, + enum class Feature { + // The device provides absolute screen coordinates. + pointer, + + // The device has an interface for providing text input. + keyboard, + + // The device has a motion tracker, such as an HMD. + tracker, + + // The device can produce force feedback. + vibration, + + // The device provides information about battery life. + battery, }; enum class Axis { @@ -161,7 +175,7 @@ PUBLISHED: }; protected: - InputDevice(const std::string &name, DeviceClass dev_class, int flags); + InputDevice(const std::string &name, DeviceClass dev_class); public: InputDevice(); @@ -177,6 +191,12 @@ public: INLINE bool is_connected() const; INLINE DeviceClass get_device_class() const; + INLINE bool has_pointer() const; + INLINE bool has_keyboard() const; + INLINE bool has_tracker() const; + INLINE bool has_vibration() const; + INLINE bool has_battery() const; + INLINE PointerData get_pointer() const; INLINE TrackerData get_tracker() const; INLINE BatteryData get_battery() const; @@ -216,11 +236,8 @@ PUBLISHED: // this could not be determined, it is set to DC_unknown. MAKE_PROPERTY(device_class, get_device_class); - INLINE bool has_pointer() const; - INLINE bool has_keyboard() const; - INLINE bool has_tracker() const; - INLINE bool has_vibration() const; - INLINE bool has_battery() const; + // Determine supported features + INLINE bool has_feature(Feature feature) const; // Getters for the various types of device data. MAKE_PROPERTY2(pointer, has_pointer, get_pointer); @@ -255,6 +272,8 @@ PUBLISHED: static std::string format_axis(Axis axis); protected: + INLINE void enable_feature(Feature feature); + // Called during the constructor to add new axes or buttons int add_button(ButtonHandle handle); int add_axis(Axis axis, int minimum, int maximum, bool centered); @@ -280,40 +299,21 @@ public: void write_buttons(std::ostream &out, int indent_level) const; void write_axes(std::ostream &out, int indent_level) const; -protected: - enum InputDeviceFlags { - // The device provides absolute screen coordinates. - IDF_has_pointer = 0x01, - - // The device has an interface for providing text input. - IDF_has_keyboard = 0x02, - - // The device has a motion tracker, such as an HMD. - IDF_has_tracker = 0x04, - - // The device can produce force feedback. - IDF_has_vibration = 0x08, - - // The device provides information about battery life. - IDF_has_battery = 0x10, - }; - protected: typedef pdeque ButtonEvents; - LightMutex _lock; + LightMutex _lock {"InputDevice"}; std::string _name; std::string _serial_number; std::string _manufacturer; - DeviceClass _device_class; - int _flags; - int _event_sequence; - unsigned short _vendor_id; - unsigned short _product_id; - bool _is_connected; - bool _enable_pointer_events; - PointerData _pointer_data; + DeviceClass _device_class = DeviceClass::unknown; + unsigned int _features = 0; + int _event_sequence = 0; + unsigned short _vendor_id = 0; + unsigned short _product_id = 0; + bool _is_connected = false; + bool _enable_pointer_events = false; PT(ButtonEventList) _button_events; PT(PointerEventList) _pointer_events; @@ -323,6 +323,7 @@ PUBLISHED: Buttons _buttons; Axes _axes; + PointerData _pointer_data; BatteryData _battery_data; TrackerData _tracker_data; diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index b3505e084f..e139a314e5 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -84,18 +84,18 @@ IOKitInputDevice(IOHIDDeviceRef device) : } if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse)) { - _device_class = DC_mouse; + _device_class = DeviceClass::mouse; } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) { - _device_class = DC_keyboard; + _device_class = DeviceClass::keyboard; } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad)) { - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_Sim_FlightStick)) { - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_GD_Joystick)) { - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; } else if (_vendor_id == 0x044f && _product_id == 0xb108) { // T.Flight Hotas X - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; } else if (_vendor_id == 0x046d && (_product_id == 0xc623 || _product_id == 0xc625 || @@ -105,9 +105,9 @@ IOKitInputDevice(IOHIDDeviceRef device) : _product_id == 0xc629 || _product_id == 0xc62b)) { // 3Dconnexion SpaceNavigator and friends. - _device_class = DC_3d_mouse; + _device_class = DeviceClass::spatial_mouse; } else if (_name == "usb gamepad") { - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; } CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, 0); @@ -127,7 +127,7 @@ IOKitInputDevice(IOHIDDeviceRef device) : } if (_pointer_x != nullptr && _pointer_y != nullptr) { - _flags |= IDF_has_pointer; + enable_feature(Feature::pointer); } _is_connected = true; @@ -182,11 +182,11 @@ parse_element(IOHIDElementRef element) { case kHIDPage_GenericDesktop: switch (usage) { case kHIDUsage_GD_X: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_x; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::roll; - } else if (_device_class == DC_mouse) { + } else if (_device_class == DeviceClass::mouse) { _pointer_x = element; return; } else { @@ -194,11 +194,11 @@ parse_element(IOHIDElementRef element) { } break; case kHIDUsage_GD_Y: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_y; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::pitch; - } else if (_device_class == DC_mouse) { + } else if (_device_class == DeviceClass::mouse) { _pointer_y = element; return; } else { @@ -206,30 +206,30 @@ parse_element(IOHIDElementRef element) { } break; case kHIDUsage_GD_Z: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_trigger; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::throttle; } else { axis = Axis::z; } break; case kHIDUsage_GD_Rx: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_x; } else { axis = Axis::pitch; } break; case kHIDUsage_GD_Ry: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_y; } else { axis = Axis::roll; } break; case kHIDUsage_GD_Rz: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_trigger; } else { axis = Axis::yaw; @@ -289,7 +289,7 @@ parse_element(IOHIDElementRef element) { // T.Flight Hotas X throttle is reversed and can go backwards. add_axis(axis, max, min, true); } else if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || - (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { + (_device_class == DeviceClass::spatial_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { // We'd like to reverse the Y axis to match the XInput behavior. // We also reverse yaw to obey the right-hand rule. add_axis(axis, max, min); @@ -627,7 +627,7 @@ parse_element(IOHIDElementRef element) { break; case kHIDPage_Button: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { if (_vendor_id == 0x0810 && _product_id == 0xe501) { // SNES-style USB gamepad static const ButtonHandle gamepad_buttons[] = { @@ -671,11 +671,11 @@ parse_element(IOHIDElementRef element) { handle = gamepad_buttons[usage]; } } - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { if (usage > 0) { handle = GamepadButton::joystick(usage - 1); } - } else if (_device_class == DC_mouse) { + } else if (_device_class == DeviceClass::mouse) { // In Panda, wheel and right button are flipped around... int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); handle = MouseButton::button(button); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index ccdd29d553..1871a1e948 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -135,9 +135,9 @@ open_device() { << " button " << (int)i << ": 0x" << std::hex << btnmap[i] << "\n"; } } else if (handle == GamepadButton::face_a()) { - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; } else if (handle == GamepadButton::trigger()) { - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; } else if (handle == GamepadButton::ltrigger()) { _ltrigger_button = i; } else if (handle == GamepadButton::rtrigger()) { @@ -156,9 +156,9 @@ open_device() { switch (axmap[i]) { case ABS_X: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_x; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = InputDevice::Axis::roll; } else { axis = InputDevice::Axis::x; @@ -166,9 +166,9 @@ open_device() { break; case ABS_Y: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_y; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = InputDevice::Axis::pitch; } else { axis = InputDevice::Axis::y; @@ -176,7 +176,7 @@ open_device() { break; case ABS_Z: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_trigger; } else { //axis = Axis::trigger; @@ -192,7 +192,7 @@ open_device() { break; case ABS_RZ: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::right_trigger; } else { axis = InputDevice::Axis::yaw; @@ -224,7 +224,7 @@ open_device() { // Emulate D-Pad or hat switch. _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { add_button(GamepadButton::dpad_left()); add_button(GamepadButton::dpad_right()); } else { @@ -240,7 +240,7 @@ open_device() { // Emulate D-Pad. _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { add_button(GamepadButton::dpad_up()); add_button(GamepadButton::dpad_down()); } else { diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index 7aa520a1fe..1af01cd4b4 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -244,15 +244,15 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { LightMutexHolder holder(_lock); - _name = move(name); + _name = std::move(name); switch (info.dwType) { case RIM_TYPEMOUSE: - _device_class = DC_mouse; + _device_class = DeviceClass::mouse; break; case RIM_TYPEKEYBOARD: - _device_class = DC_keyboard; + _device_class = DeviceClass::keyboard; break; case RIM_TYPEHID: @@ -262,27 +262,27 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // Gamepads if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_GAMEPAD) { - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; // Flight sticks } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) { - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; if (_name == "usb gamepad") { // Well, it claims to be a gamepad... - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; } // Mice } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) { - _device_class = DC_mouse; + _device_class = DeviceClass::mouse; // Keyboards } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_KEYBOARD) { - _device_class = DC_keyboard; + _device_class = DeviceClass::keyboard; // 3Dconnexion SpaceNavigator and friends. } else if (_vendor_id == 0x046d && @@ -293,7 +293,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { _product_id == 0xc628 || _product_id == 0xc629 || _product_id == 0xc62b)) { - _device_class = DC_3d_mouse; + _device_class = DeviceClass::spatial_mouse; } break; @@ -410,15 +410,15 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { ButtonHandle handle = ButtonHandle::none(); switch (cap.UsagePage) { case HID_USAGE_PAGE_BUTTON: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { if (usage < sizeof(gamepad_buttons_common) / sizeof(ButtonHandle)) { handle = gamepad_buttons[usage]; } - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { if (usage > 0) { handle = GamepadButton::joystick(usage - 1); } - } else if (_device_class == DC_mouse) { + } else if (_device_class == DeviceClass::mouse) { // In Panda, wheel and right button are flipped around... int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); handle = MouseButton::button(button); @@ -483,19 +483,19 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { case HID_USAGE_PAGE_GENERIC: switch (usage) { case HID_USAGE_GENERIC_X: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_x; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::roll; } else { axis = Axis::x; } break; case HID_USAGE_GENERIC_Y: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_y; swap(cap.LogicalMin, cap.LogicalMax); - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::pitch; } else { axis = Axis::y; @@ -503,9 +503,9 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { } break; case HID_USAGE_GENERIC_Z: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_trigger; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::throttle; } else { axis = Axis::z; @@ -513,14 +513,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { } break; case HID_USAGE_GENERIC_RX: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_x; } else { axis = Axis::pitch; } break; case HID_USAGE_GENERIC_RY: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_y; } else { axis = Axis::roll; @@ -528,7 +528,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { swap(cap.LogicalMin, cap.LogicalMax); break; case HID_USAGE_GENERIC_RZ: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_trigger; } else { // Flip to match Panda's convention for heading. @@ -570,7 +570,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // Do we need to emulate a hat switch or directional pad? if (_hat_data_index != -1) { _hat_left_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { _buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_right())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 40cb56959c..42f9c4fbef 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -315,7 +315,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { switch (caps.SubType) { default: case XINPUT_DEVSUBTYPE_GAMEPAD: - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; _axes[0].axis = Axis::left_trigger; _axes[1].axis = Axis::right_trigger; _axes[2].axis = Axis::left_x; @@ -325,7 +325,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { break; case XINPUT_DEVSUBTYPE_WHEEL: - _device_class = DC_steering_wheel; + _device_class = DeviceClass::steering_wheel; _axes[0].axis = Axis::brake; _axes[1].axis = Axis::accelerator; _axes[2].axis = Axis::wheel; @@ -335,7 +335,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { break; case XINPUT_DEVSUBTYPE_FLIGHT_STICK: - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; _axes[0].axis = Axis::yaw; _axes[1].axis = Axis::throttle; _axes[2].axis = Axis::roll; @@ -345,7 +345,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { break; case XINPUT_DEVSUBTYPE_DANCE_PAD: - _device_class = DC_dance_pad; + _device_class = DeviceClass::dance_pad; _axes[0].axis = Axis::none; _axes[1].axis = Axis::none; _axes[2].axis = Axis::none; @@ -394,7 +394,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { if (caps.Vibration.wLeftMotorSpeed != 0 || caps.Vibration.wRightMotorSpeed != 0) { - _flags |= IDF_has_vibration; + enable_feature(Feature::vibration); } if (get_battery_information != nullptr) { @@ -403,7 +403,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED && batt.BatteryType != BATTERY_TYPE_WIRED) { // This device has a battery. Report the battery level. - _flags |= IDF_has_battery; + enable_feature(Feature::battery); _battery_data.level = batt.BatteryLevel; _battery_data.max_level = BATTERY_LEVEL_FULL; } diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index c53af656f8..cba6bfdb30 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -11,13 +11,6 @@ * @date 2000-05-24 */ -/** - * - */ -INLINE GraphicsWindowInputDevice:: -GraphicsWindowInputDevice() { -} - /** * To be called by a particular kind of GraphicsWindow to indicate that the * pointer is within the window, at the given pixel coordinates. diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index 3d7102f06f..b747c64b3c 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -29,9 +29,15 @@ using std::string; * below. */ GraphicsWindowInputDevice:: -GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : - InputDevice(name, DC_virtual, flags) +GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, bool pointer, bool keyboard) : + InputDevice(name, DeviceClass::virtual_device) { + if (pointer) { + enable_feature(Feature::pointer); + } + if (keyboard) { + enable_feature(Feature::keyboard); + } } /** @@ -40,7 +46,7 @@ GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : */ PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: pointer_only(GraphicsWindow *host, const string &name) { - return new GraphicsWindowInputDevice(host, name, IDF_has_pointer); + return new GraphicsWindowInputDevice(host, name, true, false); } /** @@ -49,7 +55,7 @@ pointer_only(GraphicsWindow *host, const string &name) { */ PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: keyboard_only(GraphicsWindow *host, const string &name) { - return new GraphicsWindowInputDevice(host, name, IDF_has_keyboard); + return new GraphicsWindowInputDevice(host, name, false, true); } /** @@ -58,8 +64,7 @@ keyboard_only(GraphicsWindow *host, const string &name) { */ PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: pointer_and_keyboard(GraphicsWindow *host, const string &name) { - return new - GraphicsWindowInputDevice(host, name, IDF_has_pointer | IDF_has_keyboard); + return new GraphicsWindowInputDevice(host, name, true, true); } /** diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index 1f9d941add..aa3d97cd7b 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -27,14 +27,15 @@ class GraphicsWindow; */ class EXPCL_PANDA_DISPLAY GraphicsWindowInputDevice : public InputDevice { private: - GraphicsWindowInputDevice(GraphicsWindow *host, const std::string &name, int flags); + GraphicsWindowInputDevice(GraphicsWindow *host, const std::string &name, + bool pointer, bool keyboard); public: static PT(GraphicsWindowInputDevice) pointer_only(GraphicsWindow *host, const std::string &name); static PT(GraphicsWindowInputDevice) keyboard_only(GraphicsWindow *host, const std::string &name); static PT(GraphicsWindowInputDevice) pointer_and_keyboard(GraphicsWindow *host, const std::string &name); - INLINE GraphicsWindowInputDevice(); + GraphicsWindowInputDevice() = default; PUBLISHED: // The following interface is for the various kinds of GraphicsWindows to From 29170278e97cd8136bee3a24e003e1f35e613e61 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 19:52:26 +0100 Subject: [PATCH 79/82] device: a few InputDevice API tweaks find_button() and find_axis() should not error since there is no way to find out whether a button/axis exists or not --- panda/src/device/inputDevice.I | 22 ++++------------------ panda/src/device/inputDevice.h | 6 ++++++ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index aa8e069955..fc5236594c 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -250,13 +250,8 @@ is_button_known(size_t index) const { */ INLINE InputDevice::ButtonState InputDevice:: get_button(size_t index) const { - if (index < _buttons.size()) { - return _buttons[index]; - } else { - device_cat.error() - << "Index " << index << " was not found in the axes list\n"; - return ButtonState(); - } + nassertr_always(index < _buttons.size(), ButtonState()); + return _buttons[index]; } /** @@ -270,8 +265,6 @@ find_button(ButtonHandle handle) const { return _buttons[i]; } } - device_cat.error() - << "Handle " << handle.get_name() << " was not found in the axes list\n"; return ButtonState(); } @@ -321,13 +314,8 @@ get_axis_value(size_t index) const { */ INLINE InputDevice::AxisState InputDevice:: get_axis(size_t index) const { - if (index < _axes.size()) { - return _axes[index]; - } else { - device_cat.error() - << "Index " << index << " was not found in the axes list\n"; - return AxisState(); - } + nassertr_always(index < _axes.size(), AxisState()); + return _axes[index]; } /** @@ -341,8 +329,6 @@ find_axis(InputDevice::Axis axis) const { return _axes[i]; } } - device_cat.error() - << "Axis " << axis << " was not found in the axes list\n"; return AxisState(); } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 0088ea8f86..25bcfea9c0 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -142,6 +142,8 @@ PUBLISHED: ALWAYS_INLINE bool is_pressed() const; PUBLISHED: + operator bool() { return _state != S_unknown; } + MAKE_PROPERTY(known, is_known); MAKE_PROPERTY(pressed, is_pressed); @@ -156,6 +158,8 @@ PUBLISHED: constexpr AxisState() = default; PUBLISHED: + operator bool() { return known && value != 0.0; } + Axis axis = Axis::none; double value = 0.0; bool known = false; @@ -268,6 +272,8 @@ PUBLISHED: PT(PointerEventList) get_pointer_events(); virtual void output(std::ostream &out) const; + +public: static std::string format_device_class(DeviceClass dc); static std::string format_axis(Axis axis); From 447316c7065bd1bf3a2845eefea2d5f89ec7b677 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 20:13:13 +0100 Subject: [PATCH 80/82] samples: fixes for gamepad samples --- samples/gamepad/flightstick.py | 6 +++--- samples/gamepad/gamepad.py | 6 +++--- samples/gamepad/mappingGUI.py | 6 ++++-- samples/gamepad/steeringWheel.py | 6 +++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/samples/gamepad/flightstick.py b/samples/gamepad/flightstick.py index 0a06c52585..4520cfefb2 100644 --- a/samples/gamepad/flightstick.py +++ b/samples/gamepad/flightstick.py @@ -38,7 +38,7 @@ class App(ShowBase): # Is there a gamepad connected? self.flightStick = None - devices = self.devices.getDevices(InputDevice.DC_flight_stick) + devices = self.devices.getDevices(InputDevice.DeviceClass.flight_stick) if devices: self.connect(devices[0]) @@ -74,7 +74,7 @@ class App(ShowBase): # We're only interested if this is a flight stick and we don't have a # flight stick yet. - if device.device_class == InputDevice.DC_flight_stick and not self.flightStick: + if device.device_class == InputDevice.DeviceClass.flight_stick and not self.flightStick: print("Found %s" % (device)) self.flightStick = device @@ -98,7 +98,7 @@ class App(ShowBase): self.flightStick = None # Do we have any other gamepads? Attach the first other gamepad. - devices = self.devices.getDevices(InputDevice.DC_flight_stick) + devices = self.devices.getDevices(InputDevice.DeviceClass.flight_stick) if devices: self.connect(devices[0]) else: diff --git a/samples/gamepad/gamepad.py b/samples/gamepad/gamepad.py index c684c01253..8679a74187 100644 --- a/samples/gamepad/gamepad.py +++ b/samples/gamepad/gamepad.py @@ -63,7 +63,7 @@ class App(ShowBase): # Is there a gamepad connected? self.gamepad = None - devices = self.devices.getDevices(InputDevice.DC_gamepad) + devices = self.devices.getDevices(InputDevice.DeviceClass.gamepad) if devices: self.connect(devices[0]) @@ -99,7 +99,7 @@ class App(ShowBase): # We're only interested if this is a gamepad and we don't have a # gamepad yet. - if device.device_class == InputDevice.DC_gamepad and not self.gamepad: + if device.device_class == InputDevice.DeviceClass.gamepad and not self.gamepad: print("Found %s" % (device)) self.gamepad = device @@ -123,7 +123,7 @@ class App(ShowBase): self.gamepad = None # Do we have any other gamepads? Attach the first other gamepad. - devices = self.devices.getDevices(InputDevice.DC_gamepad) + devices = self.devices.getDevices(InputDevice.DeviceClass.gamepad) if devices: self.connect(devices[0]) else: diff --git a/samples/gamepad/mappingGUI.py b/samples/gamepad/mappingGUI.py index 84f26a9f60..17896c33a6 100644 --- a/samples/gamepad/mappingGUI.py +++ b/samples/gamepad/mappingGUI.py @@ -83,6 +83,8 @@ class ChangeActionDialog(object): self.__command = command + self.attachedDevices = [] + # Initialize the DirectGUI stuff. self.dialog = OkCancelDialog( dialogName="dlg_device_input", @@ -293,7 +295,7 @@ class MappingGUIDemo(ShowBase): devices = base.devices.getDevices() for device in devices: base.attachInputDevice(device) - self.attachedDevices = devices + self.attachedDevices = devices # Disable regular button events on all button event throwers, and # instead broadcast a generic event. @@ -324,7 +326,7 @@ class MappingGUIDemo(ShowBase): def watchControls(self, task): # move through all devices and all it's controls for device in self.attachedDevices: - if device.device_class == InputDevice.DC_mouse: + if device.device_class == InputDevice.DeviceClass.mouse: # Ignore mouse axis movement, or the user can't even navigate # to the OK/Cancel buttons! continue diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py index 65e5aa63f4..09b0d9d167 100644 --- a/samples/gamepad/steeringWheel.py +++ b/samples/gamepad/steeringWheel.py @@ -35,7 +35,7 @@ class App(ShowBase): # Is there a steering wheel connected? self.wheel = None - devices = self.devices.getDevices(InputDevice.DC_steering_wheel) + devices = self.devices.getDevices(InputDevice.DeviceClass.steering_wheel) if devices: self.connect(devices[0]) @@ -79,7 +79,7 @@ class App(ShowBase): # We're only interested if this is a steering wheel and we don't have a # wheel yet. - if device.device_class == InputDevice.DC_steering_wheel and not self.wheel: + if device.device_class == InputDevice.DeviceClass.steering_wheel and not self.wheel: print("Found %s" % (device)) self.wheel = device @@ -103,7 +103,7 @@ class App(ShowBase): self.wheel = None # Do we have any steering wheels? Attach the first other steering wheel. - devices = self.devices.getDevices(InputDevice.DC_steering_wheel) + devices = self.devices.getDevices(InputDevice.DeviceClass.steering_wheel) if devices: self.connect(devices[0]) else: From 585ba2d647a34c90ff7aeb0f2e9717db119bb27a Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 20:29:20 +0100 Subject: [PATCH 81/82] Prepare input API for multipointer support --- panda/src/device/evdevInputDevice.cxx | 15 +-- panda/src/device/inputDevice.I | 11 +- panda/src/device/inputDevice.cxx | 122 ++++++++++++------ panda/src/device/inputDevice.h | 14 +- panda/src/display/graphicsWindow.cxx | 2 +- panda/src/display/graphicsWindowInputDevice.I | 32 +++-- .../src/display/graphicsWindowInputDevice.cxx | 43 ++++++ panda/src/display/graphicsWindowInputDevice.h | 7 +- panda/src/display/mouseAndKeyboard.cxx | 24 +--- panda/src/display/mouseAndKeyboard.h | 5 +- panda/src/event/pointerEvent.I | 18 --- panda/src/event/pointerEvent.h | 27 ++-- panda/src/event/pointerEventList.I | 16 +++ panda/src/event/pointerEventList.cxx | 36 ++++++ panda/src/event/pointerEventList.h | 4 + panda/src/putil/pointerData.I | 8 ++ panda/src/putil/pointerData.h | 16 +++ panda/src/tform/mouseWatcher.I | 2 +- panda/src/tform/mouseWatcher.cxx | 2 +- panda/src/tform/mouseWatcher.h | 4 +- panda/src/windisplay/winGraphicsWindow.cxx | 2 +- panda/src/x11display/x11GraphicsWindow.cxx | 2 +- 22 files changed, 281 insertions(+), 131 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 7c0dad8c82..a5c916d8bc 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -557,6 +557,7 @@ init_device() { if (test_bit(EV_REL, evtypes)) { enable_feature(Feature::pointer); + add_pointer(PointerType::unknown, 0); } if (test_bit(EV_FF, evtypes)) { @@ -669,9 +670,8 @@ process_events() { n_read /= sizeof(struct input_event); - int x = _pointer_data.get_x(); - int y = _pointer_data.get_y(); - bool have_pointer = false; + int rel_x = 0; + int rel_y = 0; double time = ClockObject::get_global_clock()->get_frame_time(); int index; @@ -689,9 +689,8 @@ process_events() { break; case EV_REL: - if (code == REL_X) x += events[i].value; - if (code == REL_Y) y += events[i].value; - have_pointer = true; + if (code == REL_X) rel_x += events[i].value; + if (code == REL_Y) rel_y += events[i].value; break; case EV_ABS: @@ -728,8 +727,8 @@ process_events() { } } - if (have_pointer) { - set_pointer(true, x, y, time); + if (rel_x != 0 || rel_y != 0) { + pointer_moved(0, rel_x, rel_y, time); } return true; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index fc5236594c..532e77f796 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -19,6 +19,7 @@ INLINE InputDevice:: InputDevice() { _button_events = new ButtonEventList; + _pointer_events = new PointerEventList; } /** @@ -142,16 +143,6 @@ has_battery() const { return has_feature(Feature::battery); } -/** - * Returns the PointerData associated with the input device's pointer. This - * only makes sense if has_pointer() also returns true. - */ -INLINE PointerData InputDevice:: -get_pointer() const { - LightMutexHolder holder(_lock); - return _pointer_data; -} - /** * Returns the TrackerData associated with the input device's tracker. This * only makes sense if has_tracker() also returns true. diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index fc754d0d97..6f5ede4f9a 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -29,6 +29,7 @@ InputDevice(const std::string &name, DeviceClass dev_class) : _is_connected(true) { _button_events = new ButtonEventList; + _pointer_events = new PointerEventList; } /** @@ -80,7 +81,7 @@ get_button_events() { bool InputDevice:: has_pointer_event() const { LightMutexHolder holder(_lock); - return (_pointer_events != 0); + return _pointer_events != nullptr && !_pointer_events->empty(); } /** @@ -90,8 +91,8 @@ has_pointer_event() const { PT(PointerEventList) InputDevice:: get_pointer_events() { LightMutexHolder holder(_lock); - PT(PointerEventList) result = _pointer_events; - _pointer_events.clear(); + PT(PointerEventList) result = new PointerEventList; + swap(_pointer_events, result); return result; } @@ -149,69 +150,116 @@ add_axis(Axis axis, int minimum, int maximum) { } /** - * Records that a mouse movement has taken place. + * Records that a new pointer was found. + */ +int InputDevice:: +add_pointer(PointerType type, int id, bool primary) { + nassertr(_lock.debug_is_locked(), -1); + + PointerData data; + data._id = id; + data._type = type; + + int index = (int)_pointers.size(); + if (_num_pointers == _pointers.size()) { + _pointers.push_back(data); + } else { + _pointers[index] = data; + } + ++_num_pointers; + + return index; +} + +/** + * Removes a previously added pointer. If the current pressure is not zero, + * it will generate an event doing so. */ void InputDevice:: -set_pointer(bool inwin, double x, double y, double time) { +remove_pointer(int id) { nassertv(_lock.debug_is_locked()); - _pointer_data._in_window = inwin; - _pointer_data._xpos = x; - _pointer_data._ypos = y; - if (_enable_pointer_events) { - int seq = _event_sequence++; - if (_pointer_events.is_null()) { - _pointer_events = new PointerEventList(); + size_t i; + for (i = 0; i < _pointers.size(); ++i) { + if (_pointers[i]._id == id) { + break; } - _pointer_events->add_event(_pointer_data._in_window, - _pointer_data._xpos, - _pointer_data._ypos, - seq, time); + } + + if (i < _pointers.size()) { + if (_pointers[i]._pressure != 0.0) { + _pointers[i]._pressure = 0.0; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + double time = ClockObject::get_global_clock()->get_frame_time(); + _pointer_events->add_event(_pointers[i], seq, time); + } + } + + // Replace it with the last one. + if (i != _pointers.size() - 1) { + _pointers[i] = _pointers.back(); + } + --_num_pointers; } } /** - * Records that the mouse pointer has left the window. + * Records that pointer data for a pointer has changed. This can also be used + * to add a new pointer. */ void InputDevice:: -set_pointer_out_of_window(double time) { +update_pointer(PointerData data, double time) { nassertv(_lock.debug_is_locked()); - _pointer_data._in_window = false; + + PointerData *ptr = nullptr; + for (size_t i = 0; i < _pointers.size(); ++i) { + if (_pointers[i]._id == data._id) { + ptr = &_pointers[i]; + *ptr = data; + break; + } + } + if (ptr == nullptr) { + _pointers.push_back(data); + ptr = &_pointers.back(); + } if (_enable_pointer_events) { int seq = _event_sequence++; - if (_pointer_events.is_null()) { - _pointer_events = new PointerEventList(); - } - _pointer_events->add_event(_pointer_data._in_window, - _pointer_data._xpos, - _pointer_data._ypos, - seq, time); + _pointer_events->add_event(*ptr, seq, time); } } /** - * Records that a relative mouse movement has taken place. + * Records that a relative pointer movement has taken place. */ void InputDevice:: -pointer_moved(double x, double y, double time) { +pointer_moved(int id, double x, double y, double time) { nassertv(_lock.debug_is_locked()); - _pointer_data._xpos += x; - _pointer_data._ypos += y; + + PointerData *ptr = nullptr; + for (size_t i = 0; i < _pointers.size(); ++i) { + if (_pointers[i]._id == id) { + ptr = &_pointers[i]; + _pointers[i]._xpos = x; + _pointers[i]._ypos = y; + break; + } + } + nassertv_always(ptr != nullptr); if (device_cat.is_spam() && (x != 0 || y != 0)) { device_cat.spam() - << "Pointer moved by " << x << " x " << y << "\n"; + << "Pointer " << id << " moved by " << x << " x " << y << "\n"; } if (_enable_pointer_events) { int seq = _event_sequence++; - if (_pointer_events.is_null()) { - _pointer_events = new PointerEventList(); - } - _pointer_events->add_event(_pointer_data._in_window, - _pointer_data._xpos, - _pointer_data._ypos, + _pointer_events->add_event(ptr->_in_window, + ptr->_xpos, + ptr->_ypos, x, y, seq, time); } } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 25bcfea9c0..3e4e37313e 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -201,7 +201,6 @@ public: INLINE bool has_vibration() const; INLINE bool has_battery() const; - INLINE PointerData get_pointer() const; INLINE TrackerData get_tracker() const; INLINE BatteryData get_battery() const; @@ -244,7 +243,6 @@ PUBLISHED: INLINE bool has_feature(Feature feature) const; // Getters for the various types of device data. - MAKE_PROPERTY2(pointer, has_pointer, get_pointer); MAKE_PROPERTY2(tracker, has_tracker, get_tracker); MAKE_PROPERTY2(battery, has_battery, get_battery); @@ -285,10 +283,10 @@ protected: int add_axis(Axis axis, int minimum, int maximum, bool centered); int add_axis(Axis axis, int minimum, int maximum); - void set_pointer(bool inwin, double x, double y, double time); - void set_pointer_out_of_window(double time); - - void pointer_moved(double x, double y, double time); + int add_pointer(PointerType type, int id, bool primary = false); + void remove_pointer(int id); + void update_pointer(PointerData data, double time); + void pointer_moved(int id, double x, double y, double time); void button_changed(int index, bool down); void axis_changed(int index, int value); void set_axis_value(int index, double state); @@ -323,6 +321,10 @@ protected: PT(ButtonEventList) _button_events; PT(PointerEventList) _pointer_events; + size_t _num_pointers = 0; + typedef pvector Pointers; + Pointers _pointers; + PUBLISHED: typedef pvector Buttons; typedef pvector Axes; diff --git a/panda/src/display/graphicsWindow.cxx b/panda/src/display/graphicsWindow.cxx index 6ff45ed603..ee686528da 100644 --- a/panda/src/display/graphicsWindow.cxx +++ b/panda/src/display/graphicsWindow.cxx @@ -363,7 +363,7 @@ get_pointer(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData()); - result = _input_devices[device]->get_pointer(); + result = ((const GraphicsWindowInputDevice *)_input_devices[device].p())->get_pointer(); } return result; } diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index cba6bfdb30..d313af2c82 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -12,23 +12,27 @@ */ /** - * To be called by a particular kind of GraphicsWindow to indicate that the - * pointer is within the window, at the given pixel coordinates. + * Returns the PointerData associated with the input device's pointer. This + * only makes sense if has_pointer() also returns true. */ -INLINE void GraphicsWindowInputDevice:: -set_pointer_in_window(double x, double y, double time) { +INLINE PointerData GraphicsWindowInputDevice:: +get_pointer() const { LightMutexHolder holder(_lock); - InputDevice::set_pointer(true, x, y, time); + if (!_pointers.empty()) { + return _pointers[0]; + } else { + return PointerData(); + } } /** * To be called by a particular kind of GraphicsWindow to indicate that the - * pointer is no longer within the window. + * pointer data has changed. */ INLINE void GraphicsWindowInputDevice:: -set_pointer_out_of_window(double time) { +update_pointer(PointerData data, double time) { LightMutexHolder holder(_lock); - InputDevice::set_pointer_out_of_window(time); + InputDevice::update_pointer(std::move(data), time); } /** @@ -38,5 +42,15 @@ set_pointer_out_of_window(double time) { INLINE void GraphicsWindowInputDevice:: pointer_moved(double x, double y, double time) { LightMutexHolder holder(_lock); - InputDevice::pointer_moved(x, y, time); + InputDevice::pointer_moved(0, x, y, time); +} + +/** + * To be called by a particular kind of GraphicsWindow to indicate that the + * pointer no longer exists. + */ +INLINE void GraphicsWindowInputDevice:: +remove_pointer(int id) { + LightMutexHolder holder(_lock); + InputDevice::remove_pointer(id); } diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index b747c64b3c..04bac6d81d 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -34,6 +34,7 @@ GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, bool pointer { if (pointer) { enable_feature(Feature::pointer); + add_pointer(PointerType::mouse, 0); } if (keyboard) { enable_feature(Feature::keyboard); @@ -156,3 +157,45 @@ raw_button_up(ButtonHandle button, double time) { LightMutexHolder holder(_lock); _button_events->add_event(ButtonEvent(button, ButtonEvent::T_raw_up, time)); } + +/** + * To be called by a particular kind of GraphicsWindow to indicate that the + * pointer is within the window, at the given pixel coordinates. + */ +void GraphicsWindowInputDevice:: +set_pointer_in_window(double x, double y, double time) { + LightMutexHolder holder(_lock); + PointerData data = _pointers[0]; + data._id = 0; + data._type = PointerType::mouse; + data._xpos = x; + data._ypos = y; + data._in_window = true; + InputDevice::update_pointer(data, time); +} + +/** + * To be called by a particular kind of GraphicsWindow to indicate that the + * pointer is no longer within the window. + */ +void GraphicsWindowInputDevice:: +set_pointer_out_of_window(double time) { + LightMutexHolder holder(_lock); + if (_pointers.empty()) { + return; + } + + _pointers[0]._in_window = false; + _pointers[0]._pressure = 0.0; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + if (_pointer_events.is_null()) { + _pointer_events = new PointerEventList(); + } + _pointer_events->add_event(_pointers[0]._in_window, + _pointers[0]._xpos, + _pointers[0]._ypos, + seq, time); + } +} diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index aa3d97cd7b..22317a1ab0 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -53,9 +53,12 @@ PUBLISHED: void raw_button_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); void raw_button_up(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); - INLINE void set_pointer_in_window(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); - INLINE void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE PointerData get_pointer() const; + void set_pointer_in_window(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); + void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE void update_pointer(PointerData data, double time = ClockObject::get_global_clock()->get_frame_time()); INLINE void pointer_moved(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE void remove_pointer(int id); private: typedef pset ButtonsHeld; diff --git a/panda/src/display/mouseAndKeyboard.cxx b/panda/src/display/mouseAndKeyboard.cxx index 97c6447bdb..b1eb49027d 100644 --- a/panda/src/display/mouseAndKeyboard.cxx +++ b/panda/src/display/mouseAndKeyboard.cxx @@ -27,7 +27,7 @@ MouseAndKeyboard:: MouseAndKeyboard(GraphicsWindow *window, int device, const std::string &name) : DataNode(name), _window(window), - _device(device) + _device(window->get_input_device(device)) { _pixel_xy_output = define_output("pixel_xy", EventStoreVec2::get_class_type()); _pixel_size_output = define_output("pixel_size", EventStoreVec2::get_class_type()); @@ -38,6 +38,8 @@ MouseAndKeyboard(GraphicsWindow *window, int device, const std::string &name) : _pixel_xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); _pixel_size = new EventStoreVec2(LPoint2(0.0f, 0.0f)); _xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); + + _device->enable_pointer_events(); } /** @@ -47,23 +49,9 @@ MouseAndKeyboard(GraphicsWindow *window, int device, const std::string &name) : void MouseAndKeyboard:: set_source(GraphicsWindow *window, int device) { _window = window; - _device = device; -} + _device = window->get_input_device(device); -/** - * Returns the associated source window. - */ -PT(GraphicsWindow) MouseAndKeyboard:: -get_source_window() const { - return _window; -} - -/** - * Returns the associated source device. - */ -int MouseAndKeyboard:: -get_source_device() const { - return _device; + //_device->enable_pointer_events(); } /** @@ -78,7 +66,7 @@ void MouseAndKeyboard:: do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { - PT(InputDevice) device = _window->get_input_device(_device); + GraphicsWindowInputDevice *device = (GraphicsWindowInputDevice *)_device.p(); if (device->has_button_event()) { PT(ButtonEventList) bel = device->get_button_events(); diff --git a/panda/src/display/mouseAndKeyboard.h b/panda/src/display/mouseAndKeyboard.h index 9f16b54988..66cad13dc0 100644 --- a/panda/src/display/mouseAndKeyboard.h +++ b/panda/src/display/mouseAndKeyboard.h @@ -43,9 +43,6 @@ PUBLISHED: explicit MouseAndKeyboard(GraphicsWindow *window, int device, const std::string &name); void set_source(GraphicsWindow *window, int device); - PT(GraphicsWindow) get_source_window() const; - int get_source_device() const; - protected: // Inherited from DataNode virtual void do_transmit_data(DataGraphTraverser *trav, @@ -65,7 +62,7 @@ private: PT(EventStoreVec2) _xy; PT(GraphicsWindow) _window; - int _device; + PT(InputDevice) _device; public: static TypeHandle get_class_type() { diff --git a/panda/src/event/pointerEvent.I b/panda/src/event/pointerEvent.I index 835725bd42..003cbf5df0 100644 --- a/panda/src/event/pointerEvent.I +++ b/panda/src/event/pointerEvent.I @@ -11,24 +11,6 @@ * @date 2007-09-20 */ -/** - * - */ -INLINE PointerEvent:: -PointerEvent() : - _in_window(false), - _xpos(0), - _ypos(0), - _dx(0), - _dy(0), - _length(0.0), - _direction(0.0), - _rotation(0.0), - _sequence(0), - _time(0.0) -{ -} - /** * The equality operator does not consider time significant. */ diff --git a/panda/src/event/pointerEvent.h b/panda/src/event/pointerEvent.h index cd024a5e70..c0c354efd6 100644 --- a/panda/src/event/pointerEvent.h +++ b/panda/src/event/pointerEvent.h @@ -15,7 +15,7 @@ #define POINTEREVENT_H #include "pandabase.h" -#include "mouseData.h" +#include "pointerData.h" class Datagram; class DatagramIterator; @@ -25,7 +25,7 @@ class DatagramIterator; */ class EXPCL_PANDA_EVENT PointerEvent { public: - INLINE PointerEvent(); + PointerEvent() = default; INLINE bool operator == (const PointerEvent &other) const; INLINE bool operator != (const PointerEvent &other) const; @@ -37,16 +37,19 @@ public: void read_datagram(DatagramIterator &scan); public: - bool _in_window; - int _xpos; - int _ypos; - double _dx; - double _dy; - double _length; - double _direction; - double _rotation; - int _sequence; - double _time; + bool _in_window = false; + int _id = 0; + PointerType _type = PointerType::unknown; + double _xpos = 0.0; + double _ypos = 0.0; + double _dx = 0.0; + double _dy = 0.0; + double _length = 0.0; + double _direction = 0.0; + double _pressure = 0.0; + double _rotation = 0.0; + int _sequence = 0; + double _time = 0.0; }; INLINE std::ostream &operator << (std::ostream &out, const PointerEvent &pe) { diff --git a/panda/src/event/pointerEventList.I b/panda/src/event/pointerEventList.I index 2d7f3f6d05..8309609832 100644 --- a/panda/src/event/pointerEventList.I +++ b/panda/src/event/pointerEventList.I @@ -43,6 +43,22 @@ get_num_events() const { return _events.size(); } +/** + * Returns true if the list is empty. + */ +INLINE bool PointerEventList:: +empty() const { + return _events.empty(); +} + +/** + * Returns the nth event in the list. + */ +INLINE const PointerEvent &PointerEventList:: +get_event(size_t n) const { + return _events[n]; +} + /** * Get the in-window flag of the nth event. */ diff --git a/panda/src/event/pointerEventList.cxx b/panda/src/event/pointerEventList.cxx index 2109390426..ea160ea9b9 100644 --- a/panda/src/event/pointerEventList.cxx +++ b/panda/src/event/pointerEventList.cxx @@ -76,6 +76,42 @@ write(std::ostream &out, int indent_level) const { } } +/** + * Adds a new event from the given PointerData object. + */ +void PointerEventList:: +add_event(const PointerData &data, int seq, double time) { + PointerEvent pe; + pe._in_window = data._in_window; + pe._type = data._type; + pe._id = data._id; + pe._xpos = data._xpos; + pe._ypos = data._ypos; + pe._pressure = data._pressure; + pe._sequence = seq; + pe._time = time; + if (_events.size() > 0) { + pe._dx = data._xpos - _events.back()._xpos; + pe._dy = data._ypos - _events.back()._ypos; + double ddx = pe._dx; + double ddy = pe._dy; + pe._length = csqrt(ddx*ddx + ddy*ddy); + if (pe._length > 0.0) { + pe._direction = normalize_angle(rad_2_deg(catan2(-ddy,ddx))); + } else { + pe._direction = _events.back()._direction; + } + pe._rotation = delta_angle(_events.back()._direction, pe._direction); + } else { + pe._dx = 0; + pe._dy = 0; + pe._length = 0.0; + pe._direction = 0.0; + pe._rotation = 0.0; + } + _events.push_back(pe); +} + /** * Adds a new event to the end of the list. Automatically calculates the dx, * dy, length, direction, and rotation for all but the first event. diff --git a/panda/src/event/pointerEventList.h b/panda/src/event/pointerEventList.h index c8c3444c9c..e9ae1c9610 100644 --- a/panda/src/event/pointerEventList.h +++ b/panda/src/event/pointerEventList.h @@ -48,6 +48,7 @@ PUBLISHED: INLINE void clear(); INLINE void pop_front(); + void add_event(const PointerData &data, int seq, double time); void add_event(bool in_win, int xpos, int ypos, int seq, double time); void add_event(bool in_win, int xpos, int ypos, double xdelta, double ydelta, int seq, double time); @@ -60,6 +61,9 @@ public: INLINE PointerEventList(const PointerEventList ©); INLINE void operator = (const PointerEventList ©); + INLINE bool empty() const; + INLINE const PointerEvent &get_event(size_t n) const; + virtual void output(std::ostream &out) const; void write(std::ostream &out, int indent_level = 0) const; diff --git a/panda/src/putil/pointerData.I b/panda/src/putil/pointerData.I index a23b1c464b..db508d8e71 100644 --- a/panda/src/putil/pointerData.I +++ b/panda/src/putil/pointerData.I @@ -47,6 +47,14 @@ get_id() const { return _id; } +/** + * Returns the type of pointing device. + */ +INLINE PointerType PointerData:: +get_type() const { + return _type; +} + /** * Returns the pressure of the pointer. For mice, this will be 1.0 if any * button is pressed, 0.0 otherwise. diff --git a/panda/src/putil/pointerData.h b/panda/src/putil/pointerData.h index ab0aee374e..30580cbd6a 100644 --- a/panda/src/putil/pointerData.h +++ b/panda/src/putil/pointerData.h @@ -18,6 +18,19 @@ #include "modifierButtons.h" +BEGIN_PUBLISH +/** + * Contains the types of pointer device. + */ +enum class PointerType { + unknown, + mouse, + finger, + stylus, + eraser, +}; +END_PUBLISH + /** * Holds the data that might be generated by a 2-d pointer input device, such * as the mouse in the GraphicsWindow. @@ -30,6 +43,7 @@ PUBLISHED: public: INLINE int get_id() const; + INLINE PointerType get_type() const; INLINE double get_pressure() const; void output(std::ostream &out) const; @@ -37,6 +51,7 @@ public: PUBLISHED: MAKE_PROPERTY(x, get_x); MAKE_PROPERTY(y, get_y); + MAKE_PROPERTY(type, get_type); MAKE_PROPERTY(id, get_id); MAKE_PROPERTY(in_window, get_in_window); MAKE_PROPERTY(pressure, get_pressure); @@ -46,6 +61,7 @@ public: double _xpos = 0.0; double _ypos = 0.0; double _pressure = 0.0; + PointerType _type = PointerType::unknown; int _id = 0; }; diff --git a/panda/src/tform/mouseWatcher.I b/panda/src/tform/mouseWatcher.I index f47c07e6e2..2b52834551 100644 --- a/panda/src/tform/mouseWatcher.I +++ b/panda/src/tform/mouseWatcher.I @@ -542,7 +542,7 @@ get_trail_log() const { * frame. The trail log is updated once per frame, during the process_events * operation. */ -INLINE int MouseWatcher:: +INLINE size_t MouseWatcher:: num_trail_recent() const { return _num_trail_recent; } diff --git a/panda/src/tform/mouseWatcher.cxx b/panda/src/tform/mouseWatcher.cxx index c4b5c0a6e7..9344d51d7d 100644 --- a/panda/src/tform/mouseWatcher.cxx +++ b/panda/src/tform/mouseWatcher.cxx @@ -1326,7 +1326,7 @@ do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input, const PointerEventList *this_pointer_events; DCAST_INTO_V(this_pointer_events, input.get_data(_pointer_events_input).get_ptr()); _num_trail_recent = this_pointer_events->get_num_events(); - for (int i = 0; i < _num_trail_recent; i++) { + for (size_t i = 0; i < _num_trail_recent; i++) { bool in_win = this_pointer_events->get_in_window(i); int xpos = this_pointer_events->get_xpos(i); int ypos = this_pointer_events->get_ypos(i); diff --git a/panda/src/tform/mouseWatcher.h b/panda/src/tform/mouseWatcher.h index 9f21f3d09b..f81da04ba7 100644 --- a/panda/src/tform/mouseWatcher.h +++ b/panda/src/tform/mouseWatcher.h @@ -138,7 +138,7 @@ PUBLISHED: INLINE const std::string &get_inactivity_timeout_event() const; INLINE CPT(PointerEventList) get_trail_log() const; - INLINE int num_trail_recent() const; + INLINE size_t num_trail_recent() const; void set_trail_log_duration(double duration); PT(GeomNode) get_trail_node(); void clear_trail_node(); @@ -220,7 +220,7 @@ private: LVecBase4 _frame; PT(PointerEventList) _trail_log; - int _num_trail_recent; + size_t _num_trail_recent; double _trail_log_duration; PT(GeomNode) _trail_node; diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 01d952b832..40760ca1d9 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -130,7 +130,7 @@ get_pointer(int device) const { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData()); - result = _input_devices[device]->get_pointer(); + result = ((const GraphicsWindowInputDevice *)_input_devices[device].p())->get_pointer(); // We recheck this immediately to get the most up-to-date value. POINT cpos; diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index b6af0a9219..ef86c82166 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -150,7 +150,7 @@ get_pointer(int device) const { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData()); - result = _input_devices[device]->get_pointer(); + result = ((const GraphicsWindowInputDevice *)_input_devices[device].p())->get_pointer(); // We recheck this immediately to get the most up-to-date value, but we // won't bother waiting for the lock if we can't. From f8520773e94efb4ccd63d334a4ae7f3e1718017f Mon Sep 17 00:00:00 2001 From: Sebastian Hoffmann Date: Fri, 30 Nov 2018 21:02:02 +0100 Subject: [PATCH 82/82] samples: add input device tester program Closes #234 --- samples/gamepad/device_tester.py | 456 +++++++++++++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 samples/gamepad/device_tester.py diff --git a/samples/gamepad/device_tester.py b/samples/gamepad/device_tester.py new file mode 100644 index 0000000000..a3f70b96b4 --- /dev/null +++ b/samples/gamepad/device_tester.py @@ -0,0 +1,456 @@ +#!/usr/bin/env python + +import sys + +from direct.showbase.ShowBase import ShowBase +from direct.showbase.DirectObject import DirectObject +from panda3d.core import InputDeviceManager, InputDevice +from panda3d.core import VBase4, Vec2 +from panda3d.core import TextNode +from direct.gui.DirectGui import ( + DGG, + DirectFrame, + DirectButton, + DirectLabel, + DirectScrolledFrame, + DirectSlider, +) + + +class Main(ShowBase): + def __init__(self): + super().__init__() + base.disableMouse() + self.accept("escape", sys.exit) + self.device_connectivity_monitor = DeviceConnectivityMonitor() + + +class DeviceConnectivityMonitor(DirectObject): + def __init__(self): + super().__init__() + self.mgr = InputDeviceManager.get_global_ptr() + self.create_device_menu() + + self.devices = {} + for device in self.mgr.get_devices(): + self.connect_device(device) + + self.accept("connect-device", self.connect_device) + self.accept("disconnect-device", self.disconnect_device) + + def create_device_menu(self): + self.current_panel = None + self.buttons = {} + self.devices_frame = DirectScrolledFrame( + frameSize=VBase4( + 0, + base.a2dLeft*-0.75, + base.a2dBottom - base.a2dTop, + 0, + ), + frameColor=VBase4(0, 0, 0.25, 1.0), + canvasSize=VBase4( + 0, + base.a2dLeft*-0.75, + 0, + 0, + ), + scrollBarWidth=0.08, + manageScrollBars=True, + autoHideScrollBars=True, + pos=(base.a2dLeft, 0, base.a2dTop), + parent=base.aspect2d, + ) + + self.devices_frame.setCanvasSize() + + def create_menu_button(self, device): + button = DirectButton( + command=self.switch_to_panel, + extraArgs=[device], + text=device.name, + text_scale=0.05, + text_align=TextNode.ALeft, + text_fg=VBase4(0.0, 0.0, 0.0, 1.0), + text_pos=Vec2(0.01, base.a2dBottom / 10.0), + relief=1, + pad=Vec2(0.01, 0.01), + frameColor=VBase4(0.8, 0.8, 0.8, 1.0), + frameSize=VBase4( + 0.0, + base.a2dLeft*-0.75 - 0.081, # 0.08=Scrollbar, 0.001=inaccuracy + base.a2dBottom / 5.0, + 0.0, + ), + parent=self.devices_frame.getCanvas(), + ) + self.buttons[device] = button + + def destroy_menu_button(self, device): + self.buttons[device].detach_node() + del self.buttons[device] + + def refresh_device_menu(self): + self.devices_frame['canvasSize'] = VBase4( + 0, + base.a2dLeft*-0.75, + base.a2dBottom / 5.0 * len(self.buttons), + 0, + ) + self.devices_frame.setCanvasSize() + sorted_buttons = sorted(self.buttons.items(), key=lambda i: i[0].name) + for idx, (dev, button) in enumerate(sorted_buttons): + button.set_pos( + 0, + 0, + (base.a2dBottom / 5.0) * idx, + ) + + def switch_to_panel(self, device): + if self.current_panel is not None: + self.devices[self.current_panel].hide() + self.current_panel = device + self.devices[self.current_panel].show() + + def connect_device(self, device): + self.devices[device] = DeviceMonitor(device) + self.switch_to_panel(device) + self.create_menu_button(device) + self.refresh_device_menu() + + def disconnect_device(self, device): + self.devices[device].deactivate() + del self.devices[device] + if self.current_panel == device: + self.current_panel = None + if len(self.devices) > 0: + active_device = sorted( + self.devices.keys(), + key=lambda d: d.name, + )[0] + self.switch_to_panel(active_device) + + self.destroy_menu_button(device) + self.refresh_device_menu() + + +class DeviceMonitor(DirectObject): + def __init__(self, device): + super().__init__() + self.device = device + self.create_panel() + self.activate() + self.hide() + + def activate(self): + print("Device connected") + print(" Name : {}".format(self.device.name)) + print(" Type : {}".format(self.device.device_class.name)) + print(" Manufacturer: {}".format(self.device.manufacturer)) + print(" ID : {:04x}:{:04x}".format(self.device.vendor_id, + self.device.product_id)) + axis_names = [axis.axis.name for axis in self.device.axes] + print(" Axes : {} ({})".format(len(self.device.axes), + ', '.join(axis_names))) + button_names = [button.handle.name for button in self.device.buttons] + print(" Buttons : {} ({})".format(len(self.device.buttons), + ', '.join(button_names))) + + base.attachInputDevice(self.device) + + self.task = base.taskMgr.add( + self.update, + "Monitor for {}".format(self.device.name), + sort=10, + ) + + def deactivate(self): + print("\"{}\" disconnected".format(self.device.name)) + base.taskMgr.remove(self.task) + self.panel.detach_node() + + def create_panel(self): + panel_width = base.a2dLeft * -0.25 + base.a2dRight + scroll_bar_width = 0.08 + # NOTE: -0.001 because thanks to inaccuracy the vertical bar appears... + canvas_width = panel_width - scroll_bar_width - 0.001 + canvas_height = base.a2dBottom - base.a2dTop + + self.panel = DirectScrolledFrame( + frameSize=VBase4( + 0, + panel_width, + canvas_height, + 0, + ), + frameColor=VBase4(0.8, 0.8, 0.8, 1), + canvasSize=VBase4( + 0, + canvas_width, + canvas_height, + 0, + ), + scrollBarWidth=scroll_bar_width, + manageScrollBars=True, + autoHideScrollBars=True, + pos=(base.a2dLeft * 0.25, 0, base.a2dTop), + parent=base.aspect2d, + ) + panel_canvas = self.panel.getCanvas() + offset = -0.0 + + # Style sheets + + half_width_entry = dict( + frameSize=VBase4( + 0, + canvas_width / 2, + -0.1, + 0, + ), + parent=panel_canvas, + frameColor=VBase4(0.8, 0.8, 0.8, 1), + ) + left_aligned_small_text = dict( + text_align=TextNode.ALeft, + text_scale=0.05, + text_fg=VBase4(0,0,0,1), + text_pos=(0.05, -0.06), + ) + half_width_text_frame = dict( + **half_width_entry, + **left_aligned_small_text, + ) + + header = dict( + frameSize=VBase4( + 0, + canvas_width, + -0.1, + 0, + ), + parent=panel_canvas, + frameColor=VBase4(0.6, 0.6, 0.6, 1), + text_align=TextNode.ALeft, + text_scale=0.1, + text_fg=VBase4(0,0,0,1), + text_pos=(0.05, -0.075), + ) + + # Basic device data (name, device class, manufacturer, USB ID) + + self.device_header = DirectLabel( + text="Device data", + pos=(0, 0, offset), + **header, + ) + offset -= 0.1 + + def add_data_entry(offset, label, text): + self.name = DirectLabel( + text=label, + pos=(0, 0, offset), + **half_width_text_frame, + ) + self.name = DirectLabel( + text=text, + pos=(canvas_width / 2, 0, offset), + **half_width_text_frame, + ) + + metadata = [ + ('Name', self.device.name), + ('Device class', self.device.device_class.name), + ('Manufacturer', self.device.manufacturer), + ('USB ID', + "{:04x}:{:04x}".format( + self.device.vendor_id, + self.device.product_id, + ), + ), + ] + for label, text in metadata: + add_data_entry(offset, label, text) + offset -= 0.1 + + # Axes + + self.axis_sliders = [] + if len(self.device.axes) > 0: + offset -= 0.1 + self.axes_header = DirectLabel( + text="Axes", + pos=(0, 0, offset), + **header, + ) + offset -= 0.1 + + def add_axis(offset, axis_name): + slider_width = canvas_width / 2 + label = DirectLabel( + text=axis_name, + **left_aligned_small_text, + pos=(0.05, 0, offset), + parent=panel_canvas, + ) + slider = DirectSlider( + value=0.0, + range=(-1.0, 1.0), + state=DGG.DISABLED, + frameSize=VBase4( + 0, + slider_width, + -0.1, + 0, + ), + thumb_frameSize=VBase4( + 0.0, + 0.04, + -0.04, + 0.04), + frameColor=VBase4(0.3, 0.3, 0.3, 1), + pos=(canvas_width - slider_width, 0, offset), + parent=panel_canvas, + ) + return slider + + for axis in self.device.axes: + axis_slider = add_axis(offset, axis.axis.name) + self.axis_sliders.append(axis_slider) + offset -= 0.1 + + # Buttons + + self.button_buttons = [] + if len(self.device.buttons) > 0: + offset -= 0.1 + self.buttons_header = DirectLabel( + text="Buttons", + pos=(0, 0, offset), + **header, + ) + offset -= 0.1 + + def add_button(offset, button_name): + button_width = canvas_width / 2 + label = DirectLabel( + text=button_name, + **left_aligned_small_text, + pos=(0.05, 0, offset), + parent=panel_canvas, + ) + button = DirectFrame( + frameSize=VBase4( + 0, + button_width, + -0.1, + 0, + ), + text="", + text_align=TextNode.ACenter, + text_scale=0.05, + text_fg=VBase4(0,0,0,1), + text_pos=(button_width / 2, -0.06), + frameColor=VBase4(0.3, 0.3, 0.3, 1), + pos=(canvas_width - button_width, 0, offset), + parent=panel_canvas, + ) + return button + + for i in range(len(self.device.buttons)): + button_name = self.device.buttons[i].handle.name + button_button = add_button(offset, button_name) + self.button_buttons.append(button_button) + offset -= 0.1 + + # Vibration + + self.vibration = [] + if self.device.has_feature(InputDevice.Feature.vibration): + offset -= 0.1 + self.vibration_header = DirectLabel( + text="Vibration", + pos=(0, 0, offset), + **header, + ) + offset -= 0.1 + + def add_vibration(offset, axis_name, index): + slider_width = canvas_width / 2 + label = DirectLabel( + text=axis_name, + **left_aligned_small_text, + pos=(0.05, 0, offset), + parent=panel_canvas, + ) + slider = DirectSlider( + value=0.0, + range=(0.0, 1.0), + command=self.update_vibration, + frameSize=VBase4( + 0, + slider_width, + -0.1, + 0, + ), + thumb_frameSize=VBase4( + 0.0, + 0.04, + -0.04, + 0.04), + frameColor=VBase4(0.3, 0.3, 0.3, 1), + pos=(canvas_width - slider_width, 0, offset), + parent=panel_canvas, + ) + return slider + + for index, name in enumerate(["low frequency", "high frequency"]): + self.vibration.append(add_vibration(offset, name, index)) + offset -= 0.1 + + # Resize the panel's canvas to the widgets actually in it. + if -offset > -canvas_height: + self.panel['canvasSize'] = VBase4( + 0, + canvas_width, + offset, + 0, + ) + self.panel.setCanvasSize() + + def show(self): + # FIXME: Activate update task here, and deactivate it in hide()? + self.panel.show() + + def hide(self): + self.panel.hide() + + def update_vibration(self): + low = self.vibration[0]['value'] + high = self.vibration[1]['value'] + self.device.set_vibration(low, high) + + def update(self, task): + # FIXME: There needs to be a demo of events here, too. + for idx, slider in enumerate(self.axis_sliders): + slider["value"] = self.device.axes[idx].value + for idx, button in enumerate(self.button_buttons): + if self.device.buttons[idx].known: + if self.device.buttons[idx].pressed: + button['frameColor'] = VBase4(0.0, 0.8, 0.0, 1) + button['text'] = "down" + else: + button['frameColor'] = VBase4(0.3, 0.3, 0.3, 1) + button['text'] = "up" + else: + # State is InputDevice.S_unknown. This happens if the device + # manager hasn't polled yet, and in some cases before a button + # has been pressed after the program's start. + button['frameColor'] = VBase4(0.8, 0.8, 0.0, 1) + button['text'] = "unknown" + return task.cont + + +if __name__ == '__main__': + main = Main() + main.run()