diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index fcd790e0bf..bff3b4652c 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -170,6 +170,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. @@ -294,6 +295,7 @@ class ShowBase(DirectObject.DirectObject): ## The global job manager, as imported from JobManagerGlobal. self.jobMgr = jobMgr + ## Particle manager self.particleMgr = None self.particleMgrEnabled = 0 @@ -303,6 +305,11 @@ 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.__inputDeviceNodes = {} + self.createStats() self.AppHasAudioFocus = 1 @@ -1670,6 +1677,57 @@ class ShowBase(DirectObject.DirectObject): return self.mouseWatcherNode.getModifierButtons().isDown( KeyboardButton.meta()) + def attachInputDevice(self, device, prefix=None): + """ + 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. + + If you call this, you should consider calling detachInputDevice when + you are done with the device or when it is disconnected. + """ + + # Protect against the same device being attached multiple times. + assert device not in self.__inputDeviceNodes + + 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 detachInputDevice(self, device): + """ + 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). + """ + + 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: physics = importlib.import_module('panda3d.physics') @@ -1859,6 +1917,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). @@ -3074,6 +3135,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 diff --git a/dtool/src/parser-inc/XInput.h b/dtool/src/parser-inc/XInput.h new file mode 100644 index 0000000000..0674961fc0 --- /dev/null +++ b/dtool/src/parser-inc/XInput.h @@ -0,0 +1,5 @@ +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/dtool/src/parser-inc/windows.h b/dtool/src/parser-inc/windows.h index 81343a9744..79c20acefb 100644 --- a/dtool/src/parser-inc/windows.h +++ b/dtool/src/parser-inc/windows.h @@ -38,8 +38,10 @@ 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 char BYTE; typedef unsigned long ULONG; typedef long long LONGLONG; typedef long HRESULT; diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index c6a01e7cd5..608648d5a1 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -616,6 +616,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") @@ -2440,6 +2441,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: @@ -3867,6 +3869,34 @@ if (not RUNTIME): TargetAdd('libp3cull.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3cull.in', opts=['IMOD:panda3d.core', 'ILIB:libp3cull', 'SRCDIR:panda/src/cull']) +# +# DIRECTORY: panda/src/dgraph/ +# + +if (not RUNTIME): + OPTS=['DIR:panda/src/dgraph', 'BUILDING:PANDA'] + TargetAdd('p3dgraph_composite1.obj', opts=OPTS, input='p3dgraph_composite1.cxx') + TargetAdd('p3dgraph_composite2.obj', opts=OPTS, input='p3dgraph_composite2.cxx') + + OPTS=['DIR:panda/src/dgraph'] + IGATEFILES=GetDirectoryContents('panda/src/dgraph', ["*.h", "*_composite*.cxx"]) + TargetAdd('libp3dgraph.in', opts=OPTS, input=IGATEFILES) + TargetAdd('libp3dgraph.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dgraph', 'SRCDIR:panda/src/dgraph']) + +# +# 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']) + # # DIRECTORY: panda/src/display/ # @@ -3919,34 +3949,6 @@ if (not RUNTIME): TargetAdd('libp3char.in', opts=OPTS, input=IGATEFILES) TargetAdd('libp3char.in', opts=['IMOD:panda3d.core', 'ILIB:libp3char', 'SRCDIR:panda/src/char']) -# -# DIRECTORY: panda/src/dgraph/ -# - -if (not RUNTIME): - OPTS=['DIR:panda/src/dgraph', 'BUILDING:PANDA'] - TargetAdd('p3dgraph_composite1.obj', opts=OPTS, input='p3dgraph_composite1.cxx') - TargetAdd('p3dgraph_composite2.obj', opts=OPTS, input='p3dgraph_composite2.cxx') - - OPTS=['DIR:panda/src/dgraph'] - IGATEFILES=GetDirectoryContents('panda/src/dgraph', ["*.h", "*_composite*.cxx"]) - TargetAdd('libp3dgraph.in', opts=OPTS, input=IGATEFILES) - TargetAdd('libp3dgraph.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dgraph', 'SRCDIR:panda/src/dgraph']) - -# -# 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']) - # # DIRECTORY: panda/src/pnmtext/ # @@ -4111,7 +4113,8 @@ if (not RUNTIME): if (not RUNTIME): OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ', 'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', - 'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI'] + 'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI', + 'SETUPAPI'] TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx') diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.h b/panda/src/cocoadisplay/cocoaGraphicsWindow.h index 353212f056..a49f748020 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.h +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.h @@ -89,6 +89,7 @@ private: NSUInteger _modifier_keys; UInt32 _dead_key_state; 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 7d461817f1..df3ecf3b12 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -99,9 +99,10 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, [NSApp finishLaunching]; } - GraphicsWindowInputDevice device = + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); - add_input_device(device); + _input_devices.push_back(device.p()); + _input = std::move(device); CocoaGraphicsPipe *cocoa_pipe; DCAST_INTO_V(cocoa_pipe, _pipe); @@ -981,7 +982,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) { @@ -1558,7 +1559,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; @@ -1580,15 +1581,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); } } @@ -1622,7 +1623,7 @@ handle_key_event(NSEvent *event) { } cocoadisplay_cat.spam(false) << "\n"; } - _input_devices[0].keystroke(c); + _input->keystroke(c); } } @@ -1634,6 +1635,7 @@ handle_key_event(NSEvent *event) { unichar c = [str characterAtIndex: 0]; ButtonHandle button = map_key(c); + if (button == ButtonHandle::none()) { // That done, continue trying to find out the button handle. if ([str canBeConvertedToEncoding: NSASCIIStringEncoding]) { @@ -1659,9 +1661,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); } } @@ -1672,9 +1674,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); } } } @@ -1686,14 +1688,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() @@ -1712,7 +1714,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 { @@ -1727,7 +1729,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; } @@ -1753,8 +1755,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 @@ -1777,20 +1782,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/device/analogNode.I b/panda/src/device/analogNode.I index f02b638603..a61ff00b43 100644 --- a/panda/src/device/analogNode.I +++ b/panda/src/device/analogNode.I @@ -35,10 +35,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_axes(); } /** @@ -48,10 +45,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_axis_value(index); } /** @@ -60,10 +54,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_axis_known(index); } /** diff --git a/panda/src/device/analogNode.cxx b/panda/src/device/analogNode.cxx index 59e57f4179..6f9b42811c 100644 --- a/panda/src/device/analogNode.cxx +++ b/panda/src/device/analogNode.cxx @@ -27,7 +27,7 @@ AnalogNode(ClientBase *client, const std::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 != nullptr); PT(ClientDevice) device = @@ -46,7 +46,21 @@ AnalogNode(ClientBase *client, const std::string &device_name) : return; } - _analog = DCAST(ClientAnalogDevice, device); + _analog = device; +} + +/** + * + */ +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 != nullptr); } /** @@ -67,9 +81,7 @@ write(std::ostream &out, int indent_level) const { DataNode::write(out, indent_level); if (_analog != nullptr) { - _analog->acquire(); - _analog->write_controls(out, indent_level + 2); - _analog->unlock(); + _analog->write_axes(out, indent_level + 2); } } @@ -88,19 +100,18 @@ 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)) { - 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; + } } } } - _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 11626e0ae8..2b522423d2 100644 --- a/panda/src/device/analogNode.h +++ b/panda/src/device/analogNode.h @@ -39,6 +39,7 @@ class EXPCL_PANDA_DEVICE AnalogNode : public DataNode { PUBLISHED: explicit AnalogNode(ClientBase *client, const std::string &device_name); + explicit AnalogNode(InputDevice *device); virtual ~AnalogNode(); INLINE bool is_valid() const; @@ -67,7 +68,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 c4ccc2c610..013f66e41d 100644 --- a/panda/src/device/buttonNode.I +++ b/panda/src/device/buttonNode.I @@ -17,7 +17,7 @@ */ INLINE bool ButtonNode:: is_valid() const { - return (_button != nullptr) && _button->is_connected(); + return (_device != nullptr) && _device->is_connected(); } /** @@ -28,10 +28,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(); } /** @@ -46,9 +43,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->map_button(index, button); } /** @@ -58,10 +53,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); } /** @@ -70,10 +62,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->is_button_pressed(index); } /** @@ -82,8 +71,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 67803cf658..c5f8242c01 100644 --- a/panda/src/device/buttonNode.cxx +++ b/panda/src/device/buttonNode.cxx @@ -27,7 +27,6 @@ ButtonNode(ClientBase *client, const std::string &device_name) : DataNode(device_name) { _button_events_output = define_output("button_events", ButtonEventList::get_class_type()); - _button_events = new ButtonEventList; nassertv(client != nullptr); PT(ClientDevice) device = @@ -46,7 +45,19 @@ ButtonNode(ClientBase *client, const std::string &device_name) : return; } - _button = DCAST(ClientButtonDevice, device); + _device = device; +} + +/** + * + */ +ButtonNode:: +ButtonNode(InputDevice *device) : + DataNode(device->get_name()), + _device(device) +{ + _button_events_output = define_output("button_events", ButtonEventList::get_class_type()); + _device = device; } /** @@ -66,11 +77,9 @@ void ButtonNode:: output(std::ostream &out) const { DataNode::output(out); - if (_button != nullptr) { + if (_device != nullptr) { out << " ("; - _button->acquire(); - _button->output_buttons(out); - _button->unlock(); + _device->output_buttons(out); out << ")"; } } @@ -82,10 +91,8 @@ void ButtonNode:: write(std::ostream &out, int indent_level) const { DataNode::write(out, indent_level); - if (_button != nullptr) { - _button->acquire(); - _button->write_buttons(out, indent_level + 2); - _button->unlock(); + if (_device != nullptr) { + _device->write_buttons(out, indent_level + 2); } } @@ -101,14 +108,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 02b141fbbf..2739c440a7 100644 --- a/panda/src/device/buttonNode.h +++ b/panda/src/device/buttonNode.h @@ -19,8 +19,6 @@ #include "clientBase.h" #include "clientButtonDevice.h" #include "dataNode.h" -#include "buttonEventList.h" - /** * This is the primary interface to on/off button devices associated with a @@ -35,6 +33,7 @@ class EXPCL_PANDA_DEVICE ButtonNode : public DataNode { PUBLISHED: explicit ButtonNode(ClientBase *client, const std::string &device_name); + explicit ButtonNode(InputDevice *device); virtual ~ButtonNode(); INLINE bool is_valid() const; @@ -52,7 +51,7 @@ public: virtual void write(std::ostream &out, int indent_level = 0) const; private: - PT(ClientButtonDevice) _button; + PT(InputDevice) _device; protected: // Inherited from DataNode @@ -63,7 +62,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 d392d46aba..490eedb079 100644 --- a/panda/src/device/clientAnalogDevice.I +++ b/panda/src/device/clientAnalogDevice.I @@ -11,16 +11,6 @@ * @date 2001-01-26 */ -/** - * - */ -INLINE ClientAnalogDevice::AnalogState:: -AnalogState() : - _state(0.0), - _known(false) -{ -} - /** * */ @@ -29,53 +19,3 @@ ClientAnalogDevice(ClientBase *client, const std::string &device_name): ClientDevice(client, get_class_type(), device_name) { } - -/** - * 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(); -} - -/** - * 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; -} - -/** - * 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; - } -} - -/** - * 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 eb9c72ee06..f9eef0f884 100644 --- a/panda/src/device/clientAnalogDevice.cxx +++ b/panda/src/device/clientAnalogDevice.cxx @@ -17,50 +17,11 @@ TypeHandle ClientAnalogDevice::_type_handle; - - -/** - * 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()); - } -} - /** * */ void ClientAnalogDevice:: write(std::ostream &out, int indent_level) const { - indent(out, indent_level) << get_type() << " " << get_device_name() << ":\n"; - write_controls(out, indent_level + 2); -} - -/** - * Writes a multi-line description of the current analog control states. - */ -void ClientAnalogDevice:: -write_controls(std::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"; - } + indent(out, indent_level) << get_type() << " " << get_name() << ":\n"; + write_axes(out, indent_level + 2); } diff --git a/panda/src/device/clientAnalogDevice.h b/panda/src/device/clientAnalogDevice.h index 436b399240..0db13d573f 100644 --- a/panda/src/device/clientAnalogDevice.h +++ b/panda/src/device/clientAnalogDevice.h @@ -31,30 +31,7 @@ protected: INLINE ClientAnalogDevice(ClientBase *client, const std::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(std::ostream &out, int indent_level = 0) const; - void write_controls(std::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 b7c6c3347c..a6f8bb3051 100644 --- a/panda/src/device/clientBase.cxx +++ b/panda/src/device/clientBase.cxx @@ -128,7 +128,6 @@ get_device(TypeHandle device_type, const std::string &device_name) { if (device != nullptr) { 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 90aafce0c5..5b83d2947b 100644 --- a/panda/src/device/clientButtonDevice.I +++ b/panda/src/device/clientButtonDevice.I @@ -10,91 +10,3 @@ * @author drose * @date 2001-01-26 */ - -/** - * - */ -INLINE ClientButtonDevice::ButtonState:: -ButtonState() : - _handle(ButtonHandle::none()), - _state(S_unknown) -{ -} - - -/** - * 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(); -} - -/** - * 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; -} - -/** - * 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(); - } -} - -/** - * 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; - } -} - -/** - * 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; - } -} - -/** - * 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 27451c2887..9ae2b4844c 100644 --- a/panda/src/device/clientButtonDevice.cxx +++ b/panda/src/device/clientButtonDevice.cxx @@ -26,41 +26,6 @@ ClientButtonDevice:: ClientButtonDevice(ClientBase *client, const std::string &device_name): ClientDevice(client, get_class_type(), device_name) { - _button_events = new ButtonEventList(); -} - - -/** - * 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)); - } -} - - -/** - * 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()); - } } /** @@ -68,7 +33,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 << ")"; } @@ -78,68 +43,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); } - -/** - * 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"; - } -} - -/** - * 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 3b089b707b..3e5b9c57df 100644 --- a/panda/src/device/clientButtonDevice.h +++ b/panda/src/device/clientButtonDevice.h @@ -34,46 +34,9 @@ protected: ClientButtonDevice(ClientBase *client, const std::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(std::ostream &out) const; virtual void write(std::ostream &out, int indent_level = 0) const; - void output_buttons(std::ostream &out) const; - void write_buttons(std::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; @@ -90,7 +53,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 84edaf594f..5b4baba980 100644 --- a/panda/src/device/clientDevice.I +++ b/panda/src/device/clientDevice.I @@ -19,15 +19,6 @@ get_client() const { return _client; } -/** - * Returns true if the device is still connected to its ClientBase, false - * otherwise. - */ -INLINE bool ClientDevice:: -is_connected() const { - return _is_connected; -} - /** * Returns the type of device this is considered to be to the ClientBase: a * ClientTrackerDevice, ClientAnalogDevice, or what have you. This is not @@ -39,35 +30,3 @@ INLINE TypeHandle ClientDevice:: get_device_type() const { return _device_type; } - -/** - * Returns the device name reported to the ClientBase. This has some - * implementation-defined meaning to identify particular devices. - */ -INLINE const std::string &ClientDevice:: -get_device_name() const { - return _device_name; -} - -/** - * 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 -} - -/** - * 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 6da404eca4..278977f30b 100644 --- a/panda/src/device/clientDevice.cxx +++ b/panda/src/device/clientDevice.cxx @@ -24,14 +24,13 @@ TypeHandle ClientDevice::_type_handle; ClientDevice:: ClientDevice(ClientBase *client, TypeHandle device_type, const std::string &device_name) : + InputDevice(device_name, DeviceClass::unknown), _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; } /** @@ -41,7 +40,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); @@ -60,12 +59,10 @@ 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); } } @@ -79,7 +76,7 @@ disconnect() { * ClientDevice to ensure that it is fresh. */ void ClientDevice:: -poll() { +do_poll() { _client->poll(); } @@ -88,7 +85,7 @@ poll() { */ void ClientDevice:: output(std::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 0e2ad03778..b77d707484 100644 --- a/panda/src/device/clientDevice.h +++ b/panda/src/device/clientDevice.h @@ -15,12 +15,7 @@ #define CLIENTDEVICE_H #include "pandabase.h" - -#include "typedReferenceCount.h" - -#ifdef OLD_HAVE_IPC -#include -#endif +#include "inputDevice.h" class ClientBase; @@ -29,7 +24,7 @@ class ClientBase; * ClientBase, including trackers, etc. 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 std::string &device_name); @@ -39,14 +34,10 @@ public: INLINE ClientBase *get_client() const; INLINE TypeHandle get_device_type() const; - INLINE const std::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(std::ostream &out) const; virtual void write(std::ostream &out, int indent_level = 0) const; @@ -54,21 +45,15 @@ public: private: ClientBase *_client; TypeHandle _device_type; - std::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 f4be74fc4d..6ff99528e5 100644 --- a/panda/src/device/clientTrackerDevice.I +++ b/panda/src/device/clientTrackerDevice.I @@ -18,12 +18,5 @@ INLINE ClientTrackerDevice:: ClientTrackerDevice(ClientBase *client, const std::string &device_name): ClientDevice(client, get_class_type(), device_name) { -} - -/** - * Returns the TrackerData that this device is reporting. - */ -INLINE const TrackerData &ClientTrackerDevice:: -get_data() const { - return _data; + enable_feature(Feature::tracker); } diff --git a/panda/src/device/clientTrackerDevice.h b/panda/src/device/clientTrackerDevice.h index bd134a5e4c..22ff983fa0 100644 --- a/panda/src/device/clientTrackerDevice.h +++ b/panda/src/device/clientTrackerDevice.h @@ -27,12 +27,6 @@ class EXPCL_PANDA_DEVICE ClientTrackerDevice : public ClientDevice { protected: INLINE ClientTrackerDevice(ClientBase *client, const std::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 fb7f4d8928..f06c91422d 100644 --- a/panda/src/device/config_device.cxx +++ b/panda/src/device/config_device.cxx @@ -21,9 +21,12 @@ #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" +#include "xInputDevice.h" #include "dconfig.h" @@ -64,7 +67,16 @@ 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 + +#ifdef _WIN32 + XInputDevice::init_type(); +#endif } diff --git a/panda/src/device/config_device.h b/panda/src/device/config_device.h index 8a7b6542a7..0feb449b97 100644 --- a/panda/src/device/config_device.h +++ b/panda/src/device/config_device.h @@ -17,6 +17,7 @@ #include "pandabase.h" #include "notifyCategoryProxy.h" #include "configVariableBool.h" +#include "configVariableInt.h" NotifyCategoryDecl(device, EXPCL_PANDA_DEVICE, EXPTP_PANDA_DEVICE); diff --git a/panda/src/device/dialNode.I b/panda/src/device/dialNode.I index 1f6abdbc64..be59fbc019 100644 --- a/panda/src/device/dialNode.I +++ b/panda/src/device/dialNode.I @@ -26,10 +26,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(); } /** @@ -39,10 +36,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); } /** @@ -51,8 +45,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..3e0884e193 --- /dev/null +++ b/panda/src/device/evdevInputDevice.I @@ -0,0 +1,12 @@ +/** + * 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 evdevInputDevice.I + * @author rdb + * @date 2015-08-24 + */ diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx new file mode 100644 index 0000000000..a5c916d8bc --- /dev/null +++ b/panda/src/device/evdevInputDevice.cxx @@ -0,0 +1,997 @@ +/** + * 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 evdevInputDevice.cxx + * @author rdb + * @date 2015-08-24 + */ + +#include "evdevInputDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +#include "gamepadButton.h" +#include "keyboardButton.h" +#include "mouseButton.h" +#include "linuxInputDeviceManager.h" + +#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 +#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 { + // Right stick uses Z and Rz inputs. + QB_rstick_from_z = 1, + + // Throttle goes from -1 to 1 rather than from 0 to 1. + QB_centered_throttle = 2, + + // Throttle is reversed. + QB_reversed_throttle = 4, + + // 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 { + unsigned short vendor; + unsigned short product; + InputDevice::DeviceClass device_class; + int quirks; +} mapping_presets[] = { + // NVIDIA Shield Controller + {0x0955, 0x7214, InputDevice::DeviceClass::gamepad, QB_rstick_from_z}, + // T.Flight Hotas X + {0x044f, 0xb108, InputDevice::DeviceClass::flight_stick, QB_centered_throttle | QB_reversed_throttle | QB_rudder_from_throttle}, + // Xbox 360 Wireless Controller + {0x045e, 0x0719, InputDevice::DeviceClass::gamepad, QB_connect_if_nonzero}, + // Jess Tech Colour Rumble Pad + {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0}, + // 3Dconnexion Space Traveller 3D Mouse + {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0}, + // 3Dconnexion Space Pilot 3D Mouse + {0x046d, 0xc625, InputDevice::DeviceClass::spatial_mouse, 0}, + // 3Dconnexion Space Navigator 3D Mouse + {0x046d, 0xc626, InputDevice::DeviceClass::spatial_mouse, 0}, + // 3Dconnexion Space Explorer 3D Mouse + {0x046d, 0xc627, InputDevice::DeviceClass::spatial_mouse, 0}, + // 3Dconnexion Space Navigator for Notebooks + {0x046d, 0xc628, InputDevice::DeviceClass::spatial_mouse, 0}, + // 3Dconnexion SpacePilot Pro 3D Mouse + {0x046d, 0xc629, InputDevice::DeviceClass::spatial_mouse, 0}, + // 3Dconnexion Space Mouse Pro + {0x046d, 0xc62b, InputDevice::DeviceClass::spatial_mouse, 0}, + {0}, +}; + +TypeHandle EvdevInputDevice::_type_handle; + +/** + * Creates a new device representing the evdev device with the given index. + */ +EvdevInputDevice:: +EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index) : + _manager(manager), + _index(index), + _fd(-1), + _can_write(false), + _ff_id(-1), + _ff_playing(false), + _ff_strong(-1), + _ff_weak(-1), + _dpad_x_axis(-1), + _dpad_y_axis(-1), + _dpad_left_button(-1), + _dpad_up_button(-1), + _ltrigger_code(-1), + _rtrigger_code(-1) { + + char path[64]; + sprintf(path, "/dev/input/event%zd", index); + + _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(); + } else { + _is_connected = false; + device_cat.error() + << "Opening raw input device: " << strerror(errno) << " " << path << "\n"; + } +} + +/** + * + */ +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; + } +} + +/** + * 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 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; + + if (write(_fd, &play, sizeof(play)) < 0) { + device_cat.warning() + << "Failed to write force-feedback event: " << strerror(errno) << "\n"; + } + } +} + +/** + * 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 && process_events()) { + while (process_events()) {} + + // If we got events, we are obviously connected. Mark us so. + if (!_is_connected) { + _is_connected = true; + if (_manager != nullptr) { + _manager->add_device(this); + } + } + } +} + +/** + * Reads basic properties from the device. + */ +bool EvdevInputDevice:: +init_device() { + using std::string; + + nassertr(_fd >= 0, false); + + LightMutexHolder holder(_lock); + + 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) { + close(_fd); + _fd = -1; + _is_connected = false; + device_cat.error() << "Opening raw input device: ioctl failed\n"; + return false; + } + + _name.assign(name); + //cerr << "##### Now initializing device " << name << "\n"; + + struct input_id id; + if (ioctl(_fd, EVIOCGID, &id) >= 0) { + _vendor_id = id.vendor; + _product_id = id.product; + } + + bool all_values_zero = true; + bool emulate_dpad = true; + bool have_analog_triggers = false; + + bool has_keys = false; + bool has_axes = false; + + uint8_t keys[(KEY_MAX + 8) >> 3] = {0}; + if (test_bit(EV_KEY, evtypes)) { + // Check which buttons are on the device. + ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys); + has_keys = true; + + if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) { + enable_feature(Feature::keyboard); + } + } + + int num_bits = 0; + uint8_t axes[(ABS_MAX + 8) >> 3] = {0}; + if (test_bit(EV_ABS, evtypes)) { + // Check which axes are on the device. + num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; + 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 + 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[(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[(size_t)DeviceClass::steering_wheel] += 10; + } + if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) { + device_scores[(size_t)DeviceClass::steering_wheel] += 10; + } + if (test_bit(BTN_JOYSTICK, keys) && test_bit(ABS_X, axes)) { + device_scores[(size_t)DeviceClass::flight_stick] += 10; + } + if (test_bit(BTN_MOUSE, keys) && test_bit(EV_REL, evtypes)) { + 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[(size_t)DeviceClass::unknown] += 20; + } + } + if (_features & (unsigned int)Feature::keyboard) { + device_scores[(size_t)DeviceClass::keyboard] += 20; + } + + // Test for specific name tags + string lowercase_name = _name; + 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[(size_t)DeviceClass::gamepad] += 10; + } + if (lowercase_name.find("wheel") != string::npos) { + device_scores[(size_t)DeviceClass::steering_wheel] += 10; + } + if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) { + device_scores[(size_t)DeviceClass::mouse] += 10; + } + if (lowercase_name.find("keyboard") != string::npos) { + 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[(size_t)DeviceClass::unknown] += 20; + } + } + + // Check which device type got the most points + int highest_score = 0; + 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; + } + } + //std::cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; + } + + if (has_keys) { + // Also check whether the buttons are currently pressed. + uint8_t states[(KEY_MAX + 8) >> 3] = {0}; + ioctl(_fd, EVIOCGKEY(sizeof(states)), states); + + for (int i = 0; i <= KEY_MAX; ++i) { + if (test_bit(i, keys)) { + ButtonState button; + button.handle = map_button(i, _device_class); + + 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 " << button_index << ": 0x" << std::hex << i << std::dec << "\n"; + } + } + + if (test_bit(i, states)) { + button._state = S_down; + all_values_zero = false; + } else { + button._state = S_up; + } + 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 ((size_t)i >= _button_indices.size()) { + _button_indices.resize(i + 1, -1); + } + _button_indices[i] = button_index; + } + } + } + + if (has_axes) { + _axis_indices.resize(num_bits, -1); + + for (int i = 0; i < num_bits; ++i) { + if (test_bit(i, axes)) { + Axis axis = Axis::none; + switch (i) { + case ABS_X: + if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::left_x; + } else if (_device_class == DeviceClass::flight_stick) { + axis = InputDevice::Axis::roll; + } else { + axis = InputDevice::Axis::x; + } + break; + case ABS_Y: + if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::left_y; + } else if (_device_class == DeviceClass::flight_stick) { + axis = InputDevice::Axis::pitch; + } else { + axis = InputDevice::Axis::y; + } + break; + case ABS_Z: + if (quirks & QB_rstick_from_z) { + axis = InputDevice::Axis::right_x; + } else if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::left_trigger; + have_analog_triggers = true; + } else if (_device_class == DeviceClass::spatial_mouse) { + axis = InputDevice::Axis::z; + } else { + axis = InputDevice::Axis::throttle; + } + break; + case ABS_RX: + 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 == DeviceClass::spatial_mouse) { + axis = InputDevice::Axis::roll; + } else if ((quirks & QB_rstick_from_z) == 0) { + axis = InputDevice::Axis::right_y; + } + break; + case ABS_RZ: + if (quirks & QB_rstick_from_z) { + axis = InputDevice::Axis::right_y; + } else if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::right_trigger; + have_analog_triggers = true; + } else { + axis = InputDevice::Axis::yaw; + } + break; + case ABS_THROTTLE: + if (quirks & QB_rudder_from_throttle) { + axis = InputDevice::Axis::rudder; + } else { + axis = InputDevice::Axis::throttle; + } + break; + case ABS_RUDDER: + axis = InputDevice::Axis::rudder; + break; + case ABS_WHEEL: + axis = InputDevice::Axis::wheel; + break; + case ABS_GAS: + if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::right_trigger; + have_analog_triggers = true; + } else { + axis = InputDevice::Axis::accelerator; + } + break; + case ABS_BRAKE: + if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::left_trigger; + have_analog_triggers = true; + } else { + axis = InputDevice::Axis::brake; + } + break; + case ABS_HAT0X: + if (emulate_dpad) { + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + if (_device_class == DeviceClass::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(); + if (_device_class == DeviceClass::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; + } + + // Check the initial value and ranges. + struct input_absinfo absinfo; + 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 == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || + (axis == Axis::throttle && (quirks & QB_reversed_throttle) != 0) || + (_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) { + index = add_axis(axis, absinfo.minimum, absinfo.maximum, true); + } else { + index = add_axis(axis, absinfo.minimum, absinfo.maximum); + } + axis_changed(index, absinfo.value); + _axis_indices[i] = index; + + if (absinfo.value != 0) { + all_values_zero = false; + } + } + } + } + } + + if (test_bit(EV_REL, evtypes)) { + enable_feature(Feature::pointer); + add_pointer(PointerType::unknown, 0); + } + + if (test_bit(EV_FF, evtypes)) { + uint8_t effects[(FF_MAX + 8) >> 3] = {0}; + ioctl(_fd, EVIOCGBIT(EV_FF, sizeof(effects)), effects); + + if (test_bit(FF_RUMBLE, effects)) { + if (_can_write) { + enable_feature(Feature::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"; + } + } + } + + if (_ltrigger_code >= 0 && _rtrigger_code >= 0 && !have_analog_triggers) { + // Emulate analog triggers. + _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; + } + + char path[64]; + char buffer[256]; + const char *parent = ""; + 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%zd/device/device/%s../product", _index, parent); + f = fopen(path, "r"); + } + if (f) { + if (fgets(buffer, sizeof(buffer), f) != nullptr) { + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } + } + fclose(f); + } + 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) { + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + } + fclose(f); + } + 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) { + 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 && (quirks & QB_connect_if_nonzero) != 0) { + _is_connected = false; + } else { + _is_connected = true; + } + return true; +} + +/** + * 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() { + // 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. 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; + 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 rel_x = 0; + int rel_y = 0; + double time = ClockObject::get_global_clock()->get_frame_time(); + int index; + + // 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 (code == REL_X) rel_x += events[i].value; + if (code == REL_Y) rel_y += events[i].value; + break; + + case EV_ABS: + if (code == _dpad_x_axis) { + 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) { + 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 < _axis_indices.size()) break; + index = _axis_indices[code]; + if (index >= 0) { + axis_changed(index, events[i].value); + } + break; + + case EV_KEY: + nassertd(code >= 0 && (size_t)code < _button_indices.size()) break; + index = _button_indices[code]; + if (index >= 0) { + button_changed(index, events[i].value != 0); + } + if (code == _ltrigger_code) { + axis_changed(_ltrigger_axis, events[i].value); + } else if (code == _rtrigger_code) { + axis_changed(_ltrigger_axis + 1, events[i].value); + } + break; + + default: + //cerr << "event " << events[i].type << " - " << events[i].code << " - " << events[i].value << "\n"; + break; + } + } + + if (rel_x != 0 || rel_y != 0) { + pointer_moved(0, rel_x, rel_y, time); + } + + return true; +} + +/** + * Static function to map an evdev code to a ButtonHandle. + */ +ButtonHandle EvdevInputDevice:: +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[] = { + 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 == 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(); + + } 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); + } + + } else if ((code & 0xfff0) == BTN_JOYSTICK) { + if (device_class == DeviceClass::gamepad) { + // Based on "Jess Tech Colour Rumble Pad" + static const ButtonHandle mapping[] = { + GamepadButton::face_x(), + GamepadButton::face_y(), + GamepadButton::face_a(), + GamepadButton::face_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_A: + return GamepadButton::face_a(); + + case BTN_B: + return GamepadButton::face_b(); + + case BTN_C: + return GamepadButton::face_c(); + + case BTN_X: + return GamepadButton::face_x(); + + case BTN_Y: + return GamepadButton::face_y(); + + case BTN_Z: + return GamepadButton::face_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_1: + return GamepadButton::face_1(); + + case BTN_2: + return GamepadButton::face_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 BTN_THUMBL: + return GamepadButton::lstick(); + + 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(); + + 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..53c495ad08 --- /dev/null +++ b/panda/src/device/evdevInputDevice.h @@ -0,0 +1,87 @@ +/** + * 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 evdevInputDevice.h + * @author rdb + * @date 2015-08-24 + */ + +#ifndef EVDEVINPUTDEVICE_H +#define EVDEVINPUTDEVICE_H + +#include "inputDevice.h" + +#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 + * evdev API supports sending force feedback (ie. rumble) events. + */ +class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { +public: + EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index); + virtual ~EvdevInputDevice(); + +private: + virtual void do_set_vibration(double strong, double weak); + virtual void do_poll(); + + bool init_device(); + bool process_events(); + +private: + LinuxInputDeviceManager *_manager; + + int _fd; + size_t _index; + + bool _can_write; + int _ff_id; + bool _ff_playing; + int _ff_strong; + int _ff_weak; + + pvector _axis_indices; + pvector _button_indices; + + // These are used for D-pad emulation. + int _dpad_x_axis; + int _dpad_y_axis; + int _dpad_left_button; + int _dpad_up_button; + + // This is used for axis emulation. + int _ltrigger_axis; + int _ltrigger_code; + int _rtrigger_code; + +public: + static ButtonHandle map_button(int code, DeviceClass device_class = DeviceClass::unknown); + +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..532e77f796 --- /dev/null +++ b/panda/src/device/inputDevice.I @@ -0,0 +1,410 @@ +/** + * 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 inputDevice.I + * @author rdb + * @date 2015-12-11 + */ + +#include "config_device.h" + +/** + * + */ +INLINE InputDevice:: +InputDevice() { + _button_events = new ButtonEventList; + _pointer_events = new PointerEventList; +} + +/** + * Returns a human-readable name for the device. Not necessarily unique. + */ +INLINE std::string InputDevice:: +get_name() const { + LightMutexHolder holder(_lock); + return _name; +} + +/** + * Returns a string containing the manufacturer of the device, if this + * information is known. + */ +INLINE std::string InputDevice:: +get_manufacturer() const { + LightMutexHolder holder(_lock); + 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. + */ +INLINE unsigned short InputDevice:: +get_vendor_id() const { + LightMutexHolder holder(_lock); + return _vendor_id; +} + +/** + * 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; +} + +/** + * 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; +} + +/** + * Returns an identification of the general type of device. If this could not + * be determined, returns DeviceClass.unknown. + */ +INLINE InputDevice::DeviceClass InputDevice:: +get_device_class() const { + LightMutexHolder holder(_lock); + 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 { + return has_feature(Feature::pointer); +} + +/** + * Returns true if the device has a physical keyboard designed for text entry. + */ +INLINE bool InputDevice:: +has_keyboard() const { + return has_feature(Feature::keyboard); +} + +/** + * Returns true if the device features a tracker that can track position and/or + * orientation in 3D space. + */ +INLINE bool InputDevice:: +has_tracker() const { + return has_feature(Feature::tracker); +} + +/** + * Returns true if the device has vibration motors that can be controlled by + * calling set_vibration(). + */ +INLINE bool InputDevice:: +has_vibration() const { + return has_feature(Feature::vibration); +} + +/** + * Returns true if the device may be able to provide information about its + * battery life. + */ +INLINE bool InputDevice:: +has_battery() const { + return has_feature(Feature::battery); +} + +/** + * 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; +} + +/** + * Returns a rough indication of the battery level, ranging from 0 (completely + * empty battery) to the indicated max_level value. + */ +INLINE InputDevice::BatteryData InputDevice:: +get_battery() const { + LightMutexHolder holder(_lock); + return _battery_data; +} + +/** + * Returns the number of buttons known to the device. 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 size_t InputDevice:: +get_num_buttons() const { + LightMutexHolder holder(_lock); + return _buttons.size(); +} + +/** + * 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:: +map_button(size_t index, ButtonHandle button) { + LightMutexHolder holder(_lock); + if (index >= _buttons.size()) { + _buttons.resize(index + 1, ButtonState()); + } + + _buttons[index].handle = button; +} + +/** + * Returns the ButtonHandle that was previously associated with the given index + * number by a call to map_button(), or ButtonHandle::none() if no button + * was associated. + */ +INLINE ButtonHandle InputDevice:: +get_button_map(size_t index) const { + if (index < _buttons.size()) { + return _buttons[index].handle; + } else { + return ButtonHandle::none(); + } +} + +/** + * 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:: +is_button_pressed(size_t index) const { + if (index < _buttons.size()) { + return (_buttons[index]._state == S_down); + } else { + return false; + } +} + +/** + * 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(size_t index) const { + if (index < _buttons.size()) { + return _buttons[index]._state != S_unknown; + } else { + return false; + } +} + +/** + * 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:: +get_button(size_t index) const { + nassertr_always(index < _buttons.size(), ButtonState()); + return _buttons[index]; +} + +/** + * 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:: +find_button(ButtonHandle handle) const { + for (size_t i = 0; i < _buttons.size(); ++i) { + if (_buttons[i].handle == handle) { + return _buttons[i]; + } + } + return ButtonState(); +} + +/** + * 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_axes() const { + return _axes.size(); +} + +/** + * 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 axes by index number. + */ +INLINE void InputDevice:: +map_axis(size_t index, InputDevice::Axis axis) { + LightMutexHolder holder(_lock); + if (index >= _axes.size()) { + _axes.resize(index + 1, AxisState()); + } + + _axes[index].axis = axis; +} + +/** + * 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_axis_value(size_t index) const { + if (index < _axes.size()) { + return _axes[index].value; + } else { + return 0.0; + } +} + +/** + * 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::AxisState InputDevice:: +get_axis(size_t index) const { + nassertr_always(index < _axes.size(), AxisState()); + return _axes[index]; +} + +/** + * Returns the first AnalogAxis found with the given axis, or throw an assert + * if the axis was not found in the list. + */ +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]; + } + } + return AxisState(); +} + +/** + * 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_axis_known(size_t index) const { + if (index < _axes.size()) { + return _axes[index].known; + } else { + return false; + } +} + +/** + * Sets the strength of the vibration effect, if supported. The values are + * 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) { + LightMutexHolder holder(_lock); + do_set_vibration(std::max(std::min(strong, 1.0), 0.0), std::max(std::min(weak, 1.0), 0.0)); +} + +/** + * Enables the generation of mouse-movement events. + */ +INLINE void InputDevice:: +enable_pointer_events() { + LightMutexHolder holder(_lock); + _enable_pointer_events = true; +} + +/** + * Disables the generation of mouse-movement events. + */ +INLINE void InputDevice:: +disable_pointer_events() { + LightMutexHolder holder(_lock); + _enable_pointer_events = false; + _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. + */ +INLINE void InputDevice:: +set_connected(bool connected) { + LightMutexHolder holder(_lock); + _is_connected = connected; +} + +/** + * + */ +INLINE InputDevice::ButtonState:: +ButtonState(ButtonHandle handle) : + 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 new file mode 100644 index 0000000000..130854834e --- /dev/null +++ b/panda/src/device/inputDevice.cxx @@ -0,0 +1,661 @@ +/** + * 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 inputDevice.cxx + * @author rdb + * @date 2015-12-11 + */ + +#include "inputDevice.h" + +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define fma(a, b, c) ((a) * (b) + (c)) +#endif + +TypeHandle InputDevice::_type_handle; + +/** + * Defines a new InputDevice. + */ +InputDevice:: +InputDevice(const std::string &name, DeviceClass dev_class) : + _name(name), + _device_class(dev_class), + _is_connected(true) +{ + _button_events = new ButtonEventList; + _pointer_events = new PointerEventList; +} + +/** + * + */ +InputDevice:: +~InputDevice() { +} + +/** + * 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(); +} + +/** + * 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; +} + +/** + * 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; +} + +/** + * 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 != nullptr && !_pointer_events->empty(); +} + +/** + * Returns a PointerEventList containing all the recent pointer events. + * Clears the list. + */ +PT(PointerEventList) InputDevice:: +get_pointer_events() { + LightMutexHolder holder(_lock); + PT(PointerEventList) result = new PointerEventList; + swap(_pointer_events, result); + return result; +} + +/** + * Called by the implementation to add a new known button. + */ +int InputDevice:: +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. + 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; + } + int index = (int)_axes.size(); + _axes.push_back(state); + return index; +} + +/** + * 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_axis(Axis axis, int minimum, int maximum) { + bool centered = (minimum < 0) + || 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_axis(axis, minimum, maximum, centered); +} + +/** + * 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:: +remove_pointer(int id) { + nassertv(_lock.debug_is_locked()); + + size_t i; + for (i = 0; i < _pointers.size(); ++i) { + if (_pointers[i]._id == id) { + break; + } + } + + 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 pointer data for a pointer has changed. This can also be used + * to add a new pointer. + */ +void InputDevice:: +update_pointer(PointerData data, double time) { + nassertv(_lock.debug_is_locked()); + + 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++; + _pointer_events->add_event(*ptr, seq, time); + } +} + +/** + * Records that a relative pointer movement has taken place. + */ +void InputDevice:: +pointer_moved(int id, double x, double y, double time) { + nassertv(_lock.debug_is_locked()); + + 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 " << id << " moved by " << x << " x " << y << "\n"; + } + + if (_enable_pointer_events) { + int seq = _event_sequence++; + _pointer_events->add_event(ptr->_in_window, + ptr->_xpos, + ptr->_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 + * an associated ButtonHandle. The caller should ensure that the lock is held + * while this call is made. + */ +void InputDevice:: +button_changed(int index, bool down) { + nassertv(_lock.debug_is_locked()); + nassertv(index >= 0); + if (index >= (int)_buttons.size()) { + _buttons.resize(index + 1, ButtonState()); + } + + 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)); + } +} + +/** + * 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 axis within its + * total range of movement. + */ +void InputDevice:: +set_axis_value(int index, double value) { + LightMutexHolder holder(_lock); + + nassertv(index >= 0); + if ((size_t)index >= _axes.size()) { + _axes.resize((size_t)index + 1u, AxisState()); + } + + if (device_cat.is_spam() && _axes[index].value != value) { + device_cat.spam() + << "Changed axis " << index; + + if (_axes[index].axis != Axis::none) { + device_cat.spam(false) << " (" << _axes[index].axis << ")"; + } + + device_cat.spam(false) << " to " << value << "\n"; + } + + _axes[index].value = value; + _axes[index].known = true; +} + +/** + * 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:: +axis_changed(int index, int state) { + nassertv(_lock.debug_is_locked()); + nassertv(index >= 0); + if ((size_t)index >= _axes.size()) { + _axes.resize((size_t)index + 1u, AxisState()); + } + + double value = fma((double)state, _axes[index]._scale, _axes[index]._bias); + + if (device_cat.is_spam() && !IS_NEARLY_EQUAL(_axes[index].value, value)) { + device_cat.spam() + << "Changed axis " << index; + + if (_axes[index].axis != Axis::none) { + device_cat.spam(false) << " (" << _axes[index].axis << ")"; + } + + device_cat.spam(false) << " to " << value << " (raw value " << state << ")\n"; + } + + _axes[index].value = value; + _axes[index].known = true; +} + +/** + * Records that a tracker movement has taken place. + */ +void InputDevice:: +tracker_changed(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); +} + +/** + * Writes a one-line string describing the device. + */ +void InputDevice:: +output(std::ostream &out) const { + LightMutexHolder holder(_lock); + + out << _name << " ("; + + if (!_is_connected) { + out << "dis"; + } + + out << "connected)"; + + if (_device_class != DeviceClass::unknown) { + out << ", " << _device_class; + } + + if (_buttons.size() > 0) { + out << ", " << _buttons.size() << " button"; + if (_buttons.size() != 1) { + out.put('s'); + } + } + + if (_axes.size() > 0) { + out << ", " << _axes.size() << " ax" + << (_axes.size() != 1 ? 'e' : 'i') << 's'; + } + + if (_features & (unsigned int)Feature::pointer) { + out << ", pointer"; + } + if (_features & (unsigned int)Feature::keyboard) { + out << ", keyboard"; + } + if (_features & (unsigned int)Feature::tracker) { + out << ", tracker"; + } + if (_features & (unsigned int)Feature::vibration) { + out << ", vibration"; + } + if (_features & (unsigned int)Feature::battery) { + out << ", battery"; + + if (_battery_data.level > 0 && _battery_data.max_level > 0) { + out << " ["; + short i = 0; + for (; i < _battery_data.level; ++i) { + out << '='; + } + for (; i < _battery_data.max_level; ++i) { + out << ' '; + } + out << ']'; + } + } +} + +/** + * Writes a one-line string of all of the current button states. + */ +void InputDevice:: +output_buttons(std::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.is_known()) { + 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"; + } +} + +/** + * Writes a multi-line description of the current button states. + */ +void InputDevice:: +write_buttons(std::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.is_known()) { + 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"; + } +} + +/** + * Writes a multi-line description of the current analog axis states. + */ +void InputDevice:: +write_axes(std::ostream &out, int indent_level) const { + LightMutexHolder holder(_lock); + + bool any_axis = false; + Axes::const_iterator ai; + for (ai = _axes.begin(); ai != _axes.end(); ++ai) { + const AxisState &state = (*ai); + if (state.known) { + any_axis = true; + + indent(out, indent_level) + << (int)(ai - _axes.begin()) << ". " << state.value << "\n"; + } + } + + if (!any_axis) { + indent(out, indent_level) + << "(no known analog axes)\n"; + } +} + +/** + * 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) { +} + +/** + * 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() { +} + +/** + * Returns a string describing the given device class enumerant. + */ +std::string InputDevice:: +format_device_class(DeviceClass dc) { + switch (dc) { + case InputDevice::DeviceClass::unknown: + return "unknown"; + + case InputDevice::DeviceClass::virtual_device: + return "virtual_device"; + + case InputDevice::DeviceClass::keyboard: + return "keyboard"; + + case InputDevice::DeviceClass::mouse: + return "mouse"; + + case InputDevice::DeviceClass::touch: + return "touch"; + + case InputDevice::DeviceClass::gamepad: + return "gamepad"; + + case InputDevice::DeviceClass::flight_stick: + return "flight_stick"; + + case InputDevice::DeviceClass::steering_wheel: + return "steering_wheel"; + + case InputDevice::DeviceClass::dance_pad: + return "dance_pad"; + + case InputDevice::DeviceClass::hmd: + return "hmd"; + + case InputDevice::DeviceClass::spatial_mouse: + return "spatial_mouse"; + } + return "**invalid**"; +} + +/** + * Returns a string describing the given axis enumerant. + */ +std::string InputDevice:: +format_axis(Axis axis) { + switch (axis) { + case InputDevice::Axis::none: + return "none"; + + case InputDevice::Axis::x: + return "x"; + + case InputDevice::Axis::y: + return "y"; + + case InputDevice::Axis::z: + return "z"; + + case InputDevice::Axis::yaw: + return "yaw"; + + case InputDevice::Axis::pitch: + return "pitch"; + + case InputDevice::Axis::roll: + return "roll"; + + case InputDevice::Axis::left_x: + return "left_x"; + + case InputDevice::Axis::left_y: + return "left_y"; + + case InputDevice::Axis::left_trigger: + return "left_trigger"; + + case InputDevice::Axis::right_x: + return "right_x"; + + case InputDevice::Axis::right_y: + return "right_y"; + + case InputDevice::Axis::right_trigger: + return "right_trigger"; + + //case InputDevice::Axis::trigger: + // return "trigger"; + + case InputDevice::Axis::throttle: + return "throttle"; + + case InputDevice::Axis::rudder: + return "rudder"; + + case InputDevice::Axis::wheel: + return "wheel"; + + case InputDevice::Axis::accelerator: + return "accelerator"; + + case InputDevice::Axis::brake: + return "brake"; + } + return "**invalid**"; +} + +std::ostream & +operator << (std::ostream &out, InputDevice::DeviceClass dc) { + out << InputDevice::format_device_class(dc); + return out; +} + +std::ostream & +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 new file mode 100644 index 0000000000..3e4e37313e --- /dev/null +++ b/panda/src/device/inputDevice.h @@ -0,0 +1,366 @@ +/** + * 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 inputDevice.h + * @author rdb + * @date 2015-12-11 + */ + +#ifndef INPUTDEVICE_H +#define INPUTDEVICE_H + +#include "pandabase.h" + +#include "buttonEvent.h" +#include "buttonEventList.h" +#include "pointerEvent.h" +#include "pointerEventList.h" +#include "pointerData.h" +#include "trackerData.h" +#include "clockObject.h" + +#include "pdeque.h" +#include "pvector.h" +#include "lightMutex.h" +#include "lightMutexHolder.h" + +/** + * This is a structure representing a single input device. Input devices may + * 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 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 + * 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 of 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 class DeviceClass { + // It is not known what type of device this is. + unknown, + + // This means that the device doesn't correspond to a physical + // device, but rather to a dynamic source of input events. + virtual_device, + + // A physical, alphabetical keyboard. + keyboard, + + mouse, + touch, + + // A gamepad with action buttons, a D-pad, and thumbsticks. + gamepad, + + flight_stick, + steering_wheel, + dance_pad, + + // Head-mounted display. + hmd, + + // 3D mouse, such as produced by 3Dconnexion. + spatial_mouse, + }; + + 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 { + none, + + // Generic translational axes + x, + y, + z, + + // Generic rotational axes, used by joysticks and 3D mice + yaw, + pitch, + roll, + + // Gamepad + left_x, + left_y, + left_trigger, + right_x, + right_y, + right_trigger, + + // Flight stick specific + throttle, + rudder, // When available separately from yaw + + // Steering wheel / pedals + wheel, + accelerator, + 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: + operator bool() { return _state != S_unknown; } + + 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: + operator bool() { return known && value != 0.0; } + + 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); + +public: + InputDevice(); + InputDevice(const InputDevice ©) = delete; + InputDevice &operator = (const InputDevice ©) = delete; + virtual ~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 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 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); + + // 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); + + // 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); + + // Determine supported features + INLINE bool has_feature(Feature feature) const; + + // Getters for the various types of device data. + MAKE_PROPERTY2(tracker, has_tracker, get_tracker); + MAKE_PROPERTY2(battery, has_battery, get_battery); + + // Make device buttons and axes iterable + MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button); + MAKE_SEQ_PROPERTY(axes, get_num_axes, get_axis); + + // 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(); + 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(std::ostream &out) const; + +public: + static std::string format_device_class(DeviceClass dc); + 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); + int add_axis(Axis axis, int minimum, int maximum); + + 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); + + void tracker_changed(const LPoint3 &pos, const LOrientation &orient, double time); + + virtual void do_set_vibration(double low, double high); + virtual void do_poll(); + +public: + INLINE void set_connected(bool connected); + + void output_buttons(std::ostream &out) const; + void write_buttons(std::ostream &out, int indent_level) const; + void write_axes(std::ostream &out, int indent_level) const; + +protected: + typedef pdeque ButtonEvents; + + LightMutex _lock {"InputDevice"}; + + std::string _name; + std::string _serial_number; + std::string _manufacturer; + 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; + + size_t _num_pointers = 0; + typedef pvector Pointers; + Pointers _pointers; + +PUBLISHED: + typedef pvector Buttons; + typedef pvector Axes; + Buttons _buttons; + Axes _axes; + + PointerData _pointer_data; + BatteryData _battery_data; + 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 std::ostream &operator << (std::ostream &out, const InputDevice &device) { + device.output(out); + return out; +} + +EXPCL_PANDA_DEVICE std::ostream &operator << (std::ostream &out, InputDevice::DeviceClass dc); +EXPCL_PANDA_DEVICE std::ostream &operator << (std::ostream &out, InputDevice::Axis 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..731051aa3c --- /dev/null +++ b/panda/src/device/inputDeviceManager.I @@ -0,0 +1,25 @@ +/** + * 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 inputDeviceManager.I + * @author rdb + * @date 2015-12-09 + */ + +/** + * Returns the singleton InputDeviceManager instance. + */ +INLINE InputDeviceManager *InputDeviceManager:: +get_global_ptr() { + if (_global_ptr != nullptr) { + return _global_ptr; + } else { + make_global_ptr(); + return _global_ptr; + } +} diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx new file mode 100644 index 0000000000..8cd470e271 --- /dev/null +++ b/panda/src/device/inputDeviceManager.cxx @@ -0,0 +1,128 @@ +/** + * 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 inputDeviceManager.cxx + * @author rdb + * @date 2015-12-09 + */ + +#include "inputDeviceManager.h" +#include "ioKitInputDeviceManager.h" +#include "linuxInputDeviceManager.h" +#include "winInputDeviceManager.h" +#include "throw_event.h" +#include "config_putil.h" + +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. + */ +InputDeviceManager:: +InputDeviceManager() : _lock("InputDeviceManager") { +} + +/** + * Creates the global input manager. + */ +void InputDeviceManager:: +make_global_ptr() { + init_libputil(); + +#ifdef _WIN32 + _global_ptr = new WinInputDeviceManager; +#elif defined(__APPLE__) + _global_ptr = new IOKitInputDeviceManager; +#elif defined(PHAVE_LINUX_INPUT_H) + _global_ptr = new LinuxInputDeviceManager; +#else + _global_ptr = new InputDeviceManager; +#endif +} + +/** + * Description: Returns all currently connected devices. + */ +InputDeviceSet InputDeviceManager:: +get_devices() const { + InputDeviceSet devices; + LightMutexHolder holder(_lock); + + for (size_t i = 0; i < _connected_devices.size(); ++i) { + InputDevice *device = _connected_devices[i]; + 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 devices; +} + + +/** + * Called when a new device has been discovered. This may also be used to + * register virtual devices. + * + * This causes a connect-device event to be thrown. + */ +void InputDeviceManager:: +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); +} + +/** + * Called when a device has been removed, or when a device should otherwise no + * longer be tracked. + * + * This causes a disconnect-device event to be thrown. + */ +void InputDeviceManager:: +remove_device(InputDevice *device) { + { + LightMutexHolder holder(_lock); + _connected_devices.remove_device(device); + } + + throw_event("disconnect-device", device); +} + +/** + * Polls the system to see if there are any new devices. In some + * implementations this is a no-op. + */ +void InputDeviceManager:: +update() { +} diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h new file mode 100644 index 0000000000..9cfadf2845 --- /dev/null +++ b/panda/src/device/inputDeviceManager.h @@ -0,0 +1,63 @@ +/** + * 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 inputDeviceManager.h + * @author rdb + * @date 2015-12-09 + */ + +#ifndef INPUTDEVICEMANAGER_H +#define INPUTDEVICEMANAGER_H + +#include "pandabase.h" +#include "lightMutex.h" +#include "inputDevice.h" +#include "inputDeviceSet.h" + +#ifdef _WIN32 +#include "xinputDevice.h" +class WinRawInputDevice; +#endif + +/** + * 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 { +protected: + InputDeviceManager(); + ~InputDeviceManager() = default; + + static void make_global_ptr(); + +PUBLISHED: + InputDeviceSet get_devices() const; + InputDeviceSet get_devices(InputDevice::DeviceClass device_class) const; + + void add_device(InputDevice *device); + void remove_device(InputDevice *device); + + virtual void update(); + + INLINE static InputDeviceManager *get_global_ptr(); + +protected: + LightMutex _lock; + +#ifdef PHAVE_LINUX_INPUT_H + InputDeviceSet _inactive_devices; +#endif + + InputDeviceSet _connected_devices; + + static InputDeviceManager *_global_ptr; +}; + +#include "inputDeviceManager.I" + +#endif diff --git a/panda/src/device/inputDeviceNode.cxx b/panda/src/device/inputDeviceNode.cxx new file mode 100644 index 0000000000..bdf849b93b --- /dev/null +++ b/panda/src/device/inputDeviceNode.cxx @@ -0,0 +1,79 @@ +/** + * 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 std::string &name) : + DataNode(name), + _device(device) +{ + _button_events_output = define_output("button_events", ButtonEventList::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) { + + 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 new file mode 100644 index 0000000000..7fad39e5ea --- /dev/null +++ b/panda/src/device/inputDeviceNode.h @@ -0,0 +1,73 @@ +/** + * 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 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, + const DataNodeTransmit &input, + DataNodeTransmit &output); + +private: + pmap _button_states; + pmap _control_states; + + // outputs + int _button_events_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/inputDeviceSet.I b/panda/src/device/inputDeviceSet.I new file mode 100755 index 0000000000..f266055407 --- /dev/null +++ b/panda/src/device/inputDeviceSet.I @@ -0,0 +1,36 @@ +/** + * 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 inputDeviceSet.I + * @author rdb + * @date 2015-12-16 + */ + +/** + * + */ +INLINE InputDeviceSet:: +~InputDeviceSet() { +} + +/** + * Returns the nth InputDevice in the collection. + */ +INLINE InputDevice *InputDeviceSet:: +operator [] (size_t index) const { + nassertr(index < _devices.size(), nullptr); + return _devices[index]; +} + +/** + * 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..9aa27c6df4 --- /dev/null +++ b/panda/src/device/inputDeviceSet.cxx @@ -0,0 +1,141 @@ +/** + * 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 inputDeviceSet.cxx + * @author rdb + * @date 2015-12-16 + */ + +#include "inputDeviceSet.h" +#include "indent.h" + +/** + * + */ +InputDeviceSet:: +InputDeviceSet() { +} + +/** + * + */ +InputDeviceSet:: +InputDeviceSet(const InputDeviceSet ©) : + _devices(copy._devices) +{ +} + +/** + * + */ +void InputDeviceSet:: +operator = (const InputDeviceSet ©) { + _devices = copy._devices; +} + +/** + * Adds a new InputDevice to the collection. + */ +void InputDeviceSet:: +add_device(InputDevice *device) { + _devices.insert(device); +} + +/** + * Removes the indicated InputDevice from the collection. + * @return true if the device was removed, false if it was not a member. + */ +bool InputDeviceSet:: +remove_device(InputDevice *device) { + return _devices.erase(device) > 0; +} + +/** + * Adds all the InputDevices indicated in the other collection to this set. + */ +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(); +} + +/** + * Removes from this collection all of the devices listed in the other set. + */ +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); +} + +/** + * 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()); +} + +/** + * Removes all InputDevices from the collection. + */ +void InputDeviceSet:: +clear() { + _devices.clear(); +} + +/** + * This is a hint to Panda to allocate enough memory to hold the given number + * of InputDevices, if you know ahead of time how many you will be adding. + */ +void InputDeviceSet:: +reserve(size_t num) { + _devices.reserve(num); +} + +/** + * Writes a brief one-line description of the InputDeviceSet to the indicated + * output stream. + */ +void InputDeviceSet:: +output(std::ostream &out) const { + if (_devices.size() == 1) { + out << "1 input device"; + } else { + out << _devices.size() << " input devices"; + } +} + +/** + * Writes a complete multi-line description of the InputDeviceSet to the + * indicated output stream. + */ +void InputDeviceSet:: +write(std::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..e9dfdef366 --- /dev/null +++ b/panda/src/device/inputDeviceSet.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 inputDeviceSet.h + * @author rdb + * @date 2015-12-16 + */ + +#ifndef INPUTDEVICESET_H +#define INPUTDEVICESET_H + +#include "pandabase.h" +#include "ordered_vector.h" +#include "inputDevice.h" + +/** + * 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(std::ostream &out) const; + void write(std::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 std::ostream &operator << (std::ostream &out, const InputDeviceSet &col) { + col.output(out); + return out; +} + +#include "inputDeviceSet.I" + +#endif diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx new file mode 100644 index 0000000000..da1369932e --- /dev/null +++ b/panda/src/device/ioKitInputDevice.cxx @@ -0,0 +1,798 @@ +/** + * 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) { + buffer[0] = 0; + CFStringGetCString(name, buffer, sizeof(buffer), kCFStringEncodingUTF8); + + // 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)); + 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 = DeviceClass::mouse; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) { + _device_class = DeviceClass::keyboard; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad)) { + _device_class = DeviceClass::gamepad; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_Sim_FlightStick)) { + _device_class = DeviceClass::flight_stick; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_GD_Joystick)) { + _device_class = DeviceClass::flight_stick; + } else if (_vendor_id == 0x044f && _product_id == 0xb108) { + // T.Flight Hotas X + _device_class = DeviceClass::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 = DeviceClass::spatial_mouse; + } else if (_name == "usb gamepad") { + _device_class = DeviceClass::gamepad; + } + + CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, 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_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) { + enable_feature(Feature::pointer); + add_pointer(PointerType::unknown, 0); + } + + _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(); + Axis axis = Axis::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 == DeviceClass::gamepad) { + axis = Axis::left_x; + } else if (_device_class == DeviceClass::flight_stick) { + axis = Axis::roll; + } else if (_device_class == DeviceClass::mouse) { + _pointer_x = element; + return; + } else { + axis = Axis::x; + } + break; + case kHIDUsage_GD_Y: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::left_y; + } else if (_device_class == DeviceClass::flight_stick) { + axis = Axis::pitch; + } else if (_device_class == DeviceClass::mouse) { + _pointer_y = element; + return; + } else { + axis = Axis::y; + } + break; + case kHIDUsage_GD_Z: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::left_trigger; + } else if (_device_class == DeviceClass::flight_stick) { + axis = Axis::throttle; + } else { + axis = Axis::z; + } + break; + case kHIDUsage_GD_Rx: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::right_x; + } else { + axis = Axis::pitch; + } + break; + case kHIDUsage_GD_Ry: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::right_y; + } else { + axis = Axis::roll; + } + break; + case kHIDUsage_GD_Rz: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::right_trigger; + } else { + axis = Axis::yaw; + } + break; + case kHIDUsage_GD_Slider: + axis = Axis::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 = Axis::rudder; + break; + case kHIDUsage_Sim_Throttle: + axis = Axis::throttle; + break; + case kHIDUsage_Sim_Accelerator: + axis = Axis::accelerator; + break; + case kHIDUsage_Sim_Brake: + axis = Axis::brake; + break; + } + break; + } + if (axis != Axis::none) { + int min = IOHIDElementGetLogicalMin(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_axis(axis, max, min, true); + } else if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || + (_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); + } else { + add_axis(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 == DeviceClass::gamepad) { + if (_vendor_id == 0x0810 && _product_id == 0xe501) { + // SNES-style USB gamepad + static const ButtonHandle gamepad_buttons[] = { + ButtonHandle::none(), + GamepadButton::face_x(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_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::face_a(), + GamepadButton::face_b(), + GamepadButton::face_x(), + GamepadButton::face_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 == DeviceClass::flight_stick) { + if (usage > 0) { + handle = GamepadButton::joystick(usage - 1); + } + } 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); + } + 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); + axis_changed(i, value); + } + } + + if (_hat_element != nullptr) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _hat_element, &value_ref) == kIOReturnSuccess) { + int value = IOHIDValueGetIntegerValue(value_ref); + 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 + } + } + + 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(0, 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..f32f9c4256 --- /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_left_button; + 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/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..f028a5f153 --- /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/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx new file mode 100644 index 0000000000..ce7ce558e2 --- /dev/null +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -0,0 +1,295 @@ +/** + * 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_init(); + fcntl(_inotify_fd, F_SETFL, O_NONBLOCK); + fcntl(_inotify_fd, F_SETFD, FD_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) { + std::vector indices; + dirent *entry = readdir(dir); + while (entry != nullptr) { + size_t index; + if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%zd", &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); + + for (size_t index : indices) { + consider_add_evdev_device(index); + } + } 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(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 + // 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%zd", 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(std::move(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(std::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%zd/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) { + 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); + + 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(size_t js_index) { + char path[64]; + sprintf(path, "/dev/input/js%zd", 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(std::move(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(std::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; + + std::string name(event->name); + + if (event->mask & IN_DELETE) { + // The device was deleted. If we have it, remove it. + + 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]; + 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. + + 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); + } + } + } + + 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..c5ad6a013f --- /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(size_t index); + InputDevice *consider_add_js_device(size_t 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.I b/panda/src/device/linuxJoystickDevice.I new file mode 100644 index 0000000000..423880a300 --- /dev/null +++ b/panda/src/device/linuxJoystickDevice.I @@ -0,0 +1,12 @@ +/** + * 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 linuxJoystickDevice.I + * @author rdb + * @date 2015-08-21 + */ diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx new file mode 100644 index 0000000000..1871a1e948 --- /dev/null +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -0,0 +1,423 @@ +/** + * 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 linuxJoystickDevice.cxx + * @author rdb + * @date 2015-08-21 + */ + +#include "linuxJoystickDevice.h" +#include "evdevInputDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +#include "gamepadButton.h" +#include "linuxInputDeviceManager.h" + +#include +#include + +TypeHandle LinuxJoystickDevice::_type_handle; + +/** + * Creates a new device using the Linux joystick device with the given index. + */ +LinuxJoystickDevice:: +LinuxJoystickDevice(LinuxInputDeviceManager *manager, size_t index) : + _manager(manager), + _fd(-1), + _index(index), + _dpad_x_axis(-1), + _dpad_y_axis(-1), + _dpad_left_button(-1), + _dpad_up_button(-1), + _ltrigger_button(-1), + _rtrigger_button(-1) +{ + LightMutexHolder holder(_lock); + if (!open_device()) { + device_cat.error() + << "Could not open joystick device /dev/input/js" << index + << ": " << strerror(errno) << "\n"; + } +} + +/** + * + */ +LinuxJoystickDevice:: +~LinuxJoystickDevice() { + if (_fd != -1) { + close(_fd); + _fd = -1; + } +} + +/** + * Returns true if there are pending events. + */ +bool LinuxJoystickDevice:: +check_events() const { + unsigned int avail = 0; + ioctl(_fd, FIONREAD, &avail); + return (avail != 0); +} + +/** + * 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 && process_events()) { + while (process_events()) {} + + // If we got events, we are obviously connected. Mark us so. + if (!_is_connected) { + _is_connected = true; + if (_manager != nullptr) { + _manager->add_device(this); + } + } + } +} + +/** + * Opens or reopens the joystick device, and reads out the button and axis + * mappings. Assumes the lock is already held. + */ +bool LinuxJoystickDevice:: +open_device() { + nassertr(_lock.debug_is_locked(), false); + + char path[64]; + sprintf(path, "/dev/input/js%zd", _index); + + _fd = open(path, O_RDONLY | O_NONBLOCK); + + if (_fd == -1) { + _is_connected = false; + return false; + } + + // 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); + _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); + ioctl(_fd, JSIOCGBUTTONS, &num_buttons); + + _buttons.resize(num_buttons); + _axes.resize(num_axes); + + if (num_buttons > 0) { + uint16_t btnmap[512]; + ioctl(_fd, JSIOCGBTNMAP, btnmap); + + for (uint8_t i = 0; i < num_buttons; ++i) { + 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" << std::hex << btnmap[i] << "\n"; + } + } else if (handle == GamepadButton::face_a()) { + _device_class = DeviceClass::gamepad; + } else if (handle == GamepadButton::trigger()) { + _device_class = DeviceClass::flight_stick; + } else if (handle == GamepadButton::ltrigger()) { + _ltrigger_button = i; + } else if (handle == GamepadButton::rtrigger()) { + _rtrigger_button = i; + } + _buttons[i].handle = handle; + } + } + + if (num_axes > 0) { + uint8_t axmap[512]; + ioctl(_fd, JSIOCGAXMAP, axmap); + + for (uint8_t i = 0; i < num_axes; ++i) { + Axis axis = Axis::none; + + switch (axmap[i]) { + case ABS_X: + if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::left_x; + } else if (_device_class == DeviceClass::flight_stick) { + axis = InputDevice::Axis::roll; + } else { + axis = InputDevice::Axis::x; + } + break; + + case ABS_Y: + if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::left_y; + } else if (_device_class == DeviceClass::flight_stick) { + axis = InputDevice::Axis::pitch; + } else { + axis = InputDevice::Axis::y; + } + break; + + case ABS_Z: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::left_trigger; + } else { + //axis = Axis::trigger; + } + break; + + case ABS_RX: + axis = Axis::right_x; + break; + + case ABS_RY: + axis = Axis::right_y; + break; + + case ABS_RZ: + if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::right_trigger; + } else { + axis = InputDevice::Axis::yaw; + } + break; + + case ABS_THROTTLE: + axis = InputDevice::Axis::throttle; + break; + + case ABS_RUDDER: + axis = InputDevice::Axis::rudder; + break; + + case ABS_WHEEL: + axis = InputDevice::Axis::wheel; + break; + + case ABS_GAS: + axis = InputDevice::Axis::accelerator; + break; + + case ABS_BRAKE: + axis = InputDevice::Axis::brake; + break; + + case ABS_HAT0X: + if (_dpad_left_button == -1) { + // Emulate D-Pad or hat switch. + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + if (_device_class == DeviceClass::gamepad) { + add_button(GamepadButton::dpad_left()); + add_button(GamepadButton::dpad_right()); + } else { + add_button(GamepadButton::hat_left()); + add_button(GamepadButton::hat_right()); + } + axis = Axis::none; + } + break; + + case ABS_HAT0Y: + if (_dpad_up_button == -1) { + // Emulate D-Pad. + _dpad_y_axis = i; + _dpad_up_button = (int)_buttons.size(); + if (_device_class == DeviceClass::gamepad) { + add_button(GamepadButton::dpad_up()); + add_button(GamepadButton::dpad_down()); + } else { + add_button(GamepadButton::hat_up()); + add_button(GamepadButton::hat_down()); + } + axis = Axis::none; + } + break; + + default: + if (device_cat.is_debug()) { + device_cat.debug() << "Unmapped /dev/input/js" << _index + << " axis " << (int)i << ": 0x" << std::hex << (int)axmap[i] << "\n"; + } + axis = Axis::none; + break; + } + _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. + _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) { + _axes[i]._scale = 1.0 / -32767.0; + _axes[i]._bias = 0.0; + } else { + _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)_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; + } + + // Get additional information from sysfs. + 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) { + _vendor_id = 0; + } + fclose(f); + } + sprintf(path, "/sys/class/input/js%zd/device/id/product", _index); + f = fopen(path, "r"); + if (f) { + if (fscanf(f, "%hx", &_product_id) < 1) { + _product_id = 0; + } + fclose(f); + } + char buffer[256]; + sprintf(path, "/sys/class/input/js%zd/device/device/../product", _index); + f = fopen(path, "r"); + if (f) { + if (fgets(buffer, sizeof(buffer), f) != nullptr) { + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } + } + fclose(f); + } + sprintf(path, "/sys/class/input/js%zd/device/device/../manufacturer", _index); + f = fopen(path, "r"); + if (f) { + if (fgets(buffer, sizeof(buffer), f) != nullptr) { + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + } + fclose(f); + } + sprintf(path, "/sys/class/input/js%zd/device/device/../serial", _index); + f = fopen(path, "r"); + if (f) { + if (fgets(buffer, sizeof(buffer), f) != nullptr) { + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + } + fclose(f); + } + + // Read the init events. + 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 (const auto &control : _axes) { + if (control.value != 0.0) { + _is_connected = true; + return true; + } + } + _is_connected = false; + } else { + _is_connected = true; + } + + return true; +} + +/** + * 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() { + // 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. 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; + 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) { + if (index == _ltrigger_button) { + axis_changed(_ltrigger_control, events[i].value); + } else if (index == _rtrigger_button) { + axis_changed(_ltrigger_control + 1, events[i].value); + } + button_changed(index, (events[i].value != 0)); + + } else if (events[i].type & JS_EVENT_AXIS) { + if (index == _dpad_x_axis) { + 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) { + button_changed(_dpad_up_button, events[i].value < -1000); + button_changed(_dpad_up_button+1, events[i].value > 1000); + } + + axis_changed(index, events[i].value); + } + } + + return true; +} + +#endif diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h new file mode 100644 index 0000000000..af0a74e3e6 --- /dev/null +++ b/panda/src/device/linuxJoystickDevice.h @@ -0,0 +1,75 @@ +/** + * 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 linuxJoystickDevice.h + * @author rdb + * @date 2015-08-21 + */ + +#ifndef LINUXJOYSTICKDEVICE_H +#define LINUXJOYSTICKDEVICE_H + +#include "inputDevice.h" + +#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 { +PUBLISHED: + LinuxJoystickDevice(LinuxInputDeviceManager *manager, size_t index); + virtual ~LinuxJoystickDevice(); + + bool check_events() const; + +private: + virtual void do_poll(); + + bool open_device(); + bool process_events(); + +private: + LinuxInputDeviceManager *_manager; + + int _fd; + size_t _index; + + // These are used for D-pad / hat switch emulation. + int _dpad_x_axis; + int _dpad_y_axis; + 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; + } + 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..6e7da5b78b 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -5,4 +5,14 @@ #include "clientDevice.cxx" #include "clientDialDevice.cxx" #include "clientTrackerDevice.cxx" - +#include "evdevInputDevice.cxx" +#include "inputDevice.cxx" +#include "inputDeviceManager.cxx" +#include "inputDeviceNode.cxx" +#include "inputDeviceSet.cxx" +#include "ioKitInputDevice.cxx" +#include "ioKitInputDeviceManager.cxx" +#include "linuxInputDeviceManager.cxx" +#include "linuxJoystickDevice.cxx" +#include "winInputDeviceManager.cxx" +#include "winRawInputDevice.cxx" diff --git a/panda/src/device/p3device_composite2.cxx b/panda/src/device/p3device_composite2.cxx index 9457fca14c..674c1865fe 100644 --- a/panda/src/device/p3device_composite2.cxx +++ b/panda/src/device/p3device_composite2.cxx @@ -3,8 +3,7 @@ #include "analogNode.cxx" #include "buttonNode.cxx" #include "dialNode.cxx" -#include "mouseAndKeyboard.cxx" #include "trackerData.cxx" #include "trackerNode.cxx" #include "virtualMouse.cxx" - +#include "xInputDevice.cxx" diff --git a/panda/src/device/trackerNode.I b/panda/src/device/trackerNode.I index 99929d4af2..5370253d96 100644 --- a/panda/src/device/trackerNode.I +++ b/panda/src/device/trackerNode.I @@ -17,10 +17,9 @@ */ INLINE bool TrackerNode:: is_valid() const { - return (_tracker != nullptr) && _tracker->is_connected(); + return (!_tracker.is_null()/* && _tracker->is_connected()*/); } - /** * Returns the current position of the tracker, if it is available. */ diff --git a/panda/src/device/trackerNode.cxx b/panda/src/device/trackerNode.cxx index 0ec33af92f..697ccfd0f3 100644 --- a/panda/src/device/trackerNode.cxx +++ b/panda/src/device/trackerNode.cxx @@ -48,15 +48,15 @@ TrackerNode(ClientBase *client, const std::string &device_name) : return; } - _tracker = DCAST(ClientTrackerDevice, device); + _tracker = device; } /** * */ 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()); @@ -64,9 +64,10 @@ TrackerNode(ClientTrackerDevice *device) : _transform = TransformState::make_identity(); nassertv(device != nullptr); - ClientBase *client = device->get_client(); - nassertv(client != nullptr); - set_tracker_coordinate_system(client->get_coordinate_system()); + nassertv(device->has_tracker()); + + //TODO: get coordinate system from tracker object? + set_tracker_coordinate_system(CS_default); set_graph_coordinate_system(CS_default); } @@ -93,9 +94,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 29e88047bb..d4be61c1dd 100644 --- a/panda/src/device/trackerNode.h +++ b/panda/src/device/trackerNode.h @@ -18,21 +18,22 @@ #include "clientBase.h" #include "trackerData.h" -#include "clientTrackerDevice.h" +#include "inputDevice.h" #include "dataNode.h" #include "luse.h" #include "linmath_events.h" #include "pointerTo.h" /** - * 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. + * 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: explicit TrackerNode(ClientBase *client, const std::string &device_name); - explicit TrackerNode(ClientTrackerDevice *device); + explicit TrackerNode(InputDevice *device); virtual ~TrackerNode(); INLINE bool is_valid() const; @@ -62,7 +63,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/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx new file mode 100644 index 0000000000..b7fefc7c3e --- /dev/null +++ b/panda/src/device/winInputDeviceManager.cxx @@ -0,0 +1,391 @@ +/** + * 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). + std::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; + std::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) { + + // 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(std::wstring(wbuffer, wlen))); + 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 = std::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..3f0e829e38 --- /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..1af01cd4b4 --- /dev/null +++ b/panda/src/device/winRawInputDevice.cxx @@ -0,0 +1,684 @@ +/** + * 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, std::string name) { + using std::hex; + using std::dec; + using std::swap; + + LightMutexHolder holder(_lock); + + _name = std::move(name); + + switch (info.dwType) { + case RIM_TYPEMOUSE: + _device_class = DeviceClass::mouse; + break; + + case RIM_TYPEKEYBOARD: + _device_class = DeviceClass::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 = DeviceClass::gamepad; + + // Flight sticks + } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) { + _device_class = DeviceClass::flight_stick; + + if (_name == "usb gamepad") { + // Well, it claims to be a gamepad... + _device_class = DeviceClass::gamepad; + } + + // Mice + } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) { + _device_class = DeviceClass::mouse; + + // Keyboards + } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_KEYBOARD) { + _device_class = DeviceClass::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 = DeviceClass::spatial_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; + } + + // Do we have a button mapping? + static const ButtonHandle gamepad_buttons_common[] = { + ButtonHandle::none(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_x(), + GamepadButton::face_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::face_x(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_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/axis indices. + _indices.resize(caps.NumberInputDataIndices); + + _buttons.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)); + _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 == DeviceClass::gamepad) { + if (usage < sizeof(gamepad_buttons_common) / sizeof(ButtonHandle)) { + handle = gamepad_buttons[usage]; + } + } else if (_device_class == DeviceClass::flight_stick) { + if (usage > 0) { + handle = GamepadButton::joystick(usage - 1); + } + } 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); + } + 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; + } + + Axis axis = Axis::none; + switch (cap.UsagePage) { + case HID_USAGE_PAGE_GENERIC: + switch (usage) { + case HID_USAGE_GENERIC_X: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::left_x; + } else if (_device_class == DeviceClass::flight_stick) { + axis = Axis::roll; + } else { + axis = Axis::x; + } + break; + case HID_USAGE_GENERIC_Y: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::left_y; + swap(cap.LogicalMin, cap.LogicalMax); + } else if (_device_class == DeviceClass::flight_stick) { + axis = Axis::pitch; + } else { + axis = Axis::y; + swap(cap.LogicalMin, cap.LogicalMax); + } + break; + case HID_USAGE_GENERIC_Z: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::left_trigger; + } else if (_device_class == DeviceClass::flight_stick) { + axis = Axis::throttle; + } else { + axis = Axis::z; + swap(cap.LogicalMin, cap.LogicalMax); + } + break; + case HID_USAGE_GENERIC_RX: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::right_x; + } else { + axis = Axis::pitch; + } + break; + case HID_USAGE_GENERIC_RY: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::right_y; + } else { + axis = Axis::roll; + } + swap(cap.LogicalMin, cap.LogicalMax); + break; + case HID_USAGE_GENERIC_RZ: + if (_device_class == DeviceClass::gamepad) { + axis = Axis::right_trigger; + } else { + // Flip to match Panda's convention for heading. + axis = Axis::yaw; + swap(cap.LogicalMin, cap.LogicalMax); + } + break; + case HID_USAGE_GENERIC_SLIDER: + // Flip to match Panda's convention for heading. + axis = Axis::rudder; + swap(cap.LogicalMin, cap.LogicalMax); + break; + case HID_USAGE_GENERIC_WHEEL: + axis = Axis::wheel; + break; + case HID_USAGE_GENERIC_HATSWITCH: + // This is handled specially. + _hat_data_index = data_index; + _hat_data_minimum = cap.LogicalMin; + continue; + } + break; + } + + int axis_index; + if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { + // T.Flight Hotas X throttle is reversed and can go backwards. + 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 + axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax, true); + } else { + axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax); + } + _indices[data_index] = Index::axis(axis_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 == DeviceClass::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._axis >= 0) { + if (idx._signed) { + axis_changed(idx._axis, (SHORT)data[di].RawValue); + } else { + axis_changed(idx._axis, 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" << std::hex << (status & 0xffffffffu) << std::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..c5cba8c615 --- /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, std::string name); + void on_removal(); + void on_input(PRAWINPUT input); + +private: + virtual void do_poll(); + +private: + const std::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 axis index. + struct Index { + Index() : _button(-1), _axis(-1) {} + + static Index button(int index) { + Index idx; + idx._button = index; + return idx; + } + static Index axis(int index, bool is_signed=true) { + Index idx; + idx._axis = index; + idx._signed = is_signed; + return idx; + } + + int _button; + int _axis; + bool _signed; + }; + + // Maps a "data index" to either button index or axis 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 new file mode 100644 index 0000000000..42f9c4fbef --- /dev/null +++ b/panda/src/device/xInputDevice.cxx @@ -0,0 +1,506 @@ +/** + * 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 xInputDevice.cxx + * @author rdb + * @date 2015-07-15 + */ + +#include "xInputDevice.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +#include "gamepadButton.h" +#include "inputDeviceManager.h" +#include "string_utils.h" + +#include +#include + +#ifndef XUSER_MAX_COUNT +#define XUSER_MAX_COUNT 4 +#endif + +#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 + +#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. +typedef struct _XINPUT_BUSINFO { + WORD VendorID; + WORD ProductID; + WORD RevisionID; + WORD Unknown1; // Unknown - padding? + DWORD InstanceID; + DWORD Unknown2; + 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 = 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; + +/** + * Protected constructor. user_index is a number 0-3. + */ +XInputDevice:: +XInputDevice(DWORD user_index) : + _index(user_index), + _last_packet(-1), + _last_buttons(0) { + + nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT); + + _axes.resize(6); + _buttons.resize(16); +} + +/** + * + */ +XInputDevice:: +~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 std::string &name, const std::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) { + std::string ids(buffer, buflen); + char revstr[16]; + sprintf(revstr, "REV_%04x", caps.RevisionID); + if (ids.find(revstr) == std::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_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) && + 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); + } +} + +/** + * Static method to initialize 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"; + } + + dll_name = "Xinput1_3.dll"; + module = LoadLibraryA(dll_name); + } + + if (module) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Successfully loaded " << dll_name << "\n"; + } + + // Undocumented version (XInputGetStateEx) that includes a + // state bit for the guide button. + get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100)); + if (get_state == nullptr) { + get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState"); + if (get_state == nullptr) { + device_cat.error() + << "Failed to find function XInputGetState in " << dll_name << ".\n"; + return false; + } + } + + set_state = (pXInputSetState)GetProcAddress(module, "XInputSetState"); + 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 == nullptr) { + 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)); + get_capabilities_ex = (pXInputGetCapabilitiesEx)GetProcAddress(module, MAKEINTRESOURCE(108)); + return true; + } + + device_cat.error() + << "Failed to load Xinput1_4.dll or Xinput1_3.dll.\n"; + return false; +} + +/** + * 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) { + 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) { + + // 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 = DeviceClass::gamepad; + _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 = DeviceClass::steering_wheel; + _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 = DeviceClass::flight_stick; + _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 = DeviceClass::dance_pad; + _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; + } + + _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; + + _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) { + _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 { + _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(); + } + _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) { + enable_feature(Feature::vibration); + } + + 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 && + batt.BatteryType != BATTERY_TYPE_WIRED) { + // This device has a battery. Report the battery level. + enable_feature(Feature::battery); + _battery_data.level = batt.BatteryLevel; + _battery_data.max_level = BATTERY_LEVEL_FULL; + } + } + } + + WORD buttons = state.Gamepad.wButtons; + 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; + mask <<= 1; + if (i == 10) { + // XInput skips 0x0800. + mask <<= 1; + } + } + + 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; +} + +/** + * 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) { + nassertv_always(_is_connected); + + XINPUT_VIBRATION vibration; + vibration.wLeftMotorSpeed = strong * 0xffff; + vibration.wRightMotorSpeed = weak * 0xffff; + set_state(_index, &vibration); +} + +/** + * 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() { + // 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) { + // Device was disconnected. + if (_is_connected) { + _is_connected = false; + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->remove_device(this); + } + return; + } + + if (state.dwPacketNumber == _last_packet) { + // No change since last time we asked. + return; + } + + // Did any buttons change state? + WORD changed_buttons = _last_buttons ^ state.Gamepad.wButtons; + + WORD mask = 1; + for (int i = 0; i < 16; ++i) { + if (changed_buttons & mask) { + button_changed(i, (state.Gamepad.wButtons & mask) != 0); + } + mask <<= 1; + if (i == 10) { + // XInput skips 0x0800. + mask <<= 1; + } + } + + 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; +} + +#endif // _WIN32 diff --git a/panda/src/device/xInputDevice.h b/panda/src/device/xInputDevice.h new file mode 100644 index 0000000000..0bf3900ec4 --- /dev/null +++ b/panda/src/device/xInputDevice.h @@ -0,0 +1,60 @@ +/** + * 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 xInputDevice.h + * @author rdb + * @date 2015-07-15 + */ + +#ifndef XINPUTDEVICE_H +#define XINPUTDEVICE_H + +#include "pandabase.h" +#include "inputDevice.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +#include + +class InputDeviceManager; + +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. + */ +class EXPCL_PANDA_DEVICE XInputDevice final : public InputDevice { +public: + XInputDevice(DWORD user_index); + ~XInputDevice(); + + bool check_arrival(const RID_DEVICE_INFO &info, DEVINST inst, + const std::string &name, const std::string &manufacturer); + void detect(InputDeviceManager *mgr); + static bool init_xinput(); + +private: + 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(); + +private: + const DWORD _index; + DWORD _last_packet; + WORD _last_buttons; + + static bool _initialized; +}; + +#endif // _WIN32 + +#endif diff --git a/panda/src/display/callbackGraphicsWindow.cxx b/panda/src/display/callbackGraphicsWindow.cxx index ebe2442d17..7372f81f64 100644 --- a/panda/src/display/callbackGraphicsWindow.cxx +++ b/panda/src/display/callbackGraphicsWindow.cxx @@ -47,28 +47,13 @@ CallbackGraphicsWindow:: ~CallbackGraphicsWindow() { } - -/** - * 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]; -} - /** * Adds a new input device (mouse) to the window with the indicated name. * Returns the index of the new device. */ int CallbackGraphicsWindow:: create_input_device(const std::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 fc7d8310f5..dee8558a53 100644 --- a/panda/src/display/callbackGraphicsWindow.h +++ b/panda/src/display/callbackGraphicsWindow.h @@ -178,7 +178,6 @@ PUBLISHED: INLINE void clear_render_callback(); INLINE CallbackObject *get_render_callback() const; - GraphicsWindowInputDevice &get_input_device(int device); int create_input_device(const std::string &name); public: diff --git a/panda/src/display/config_display.cxx b/panda/src/display/config_display.cxx index 1c2f612fcc..78c0b17418 100644 --- a/panda/src/display/config_display.cxx +++ b/panda/src/display/config_display.cxx @@ -21,9 +21,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" @@ -510,7 +512,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 8d618f3d9b..ee686528da 100644 --- a/panda/src/display/graphicsWindow.cxx +++ b/panda/src/display/graphicsWindow.cxx @@ -247,6 +247,17 @@ get_num_input_devices() const { return result; } +/** + * 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]; +} + /** * Returns the name of the nth input device. */ @@ -256,7 +267,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; } @@ -271,7 +282,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; } @@ -285,7 +296,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; } @@ -306,7 +317,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(); } /** @@ -316,28 +327,30 @@ 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(); } /** * See GraphicsWindowInputDevice::enable_pointer_mode */ +/* 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); +}*/ /** * See GraphicsWindowInputDevice::disable_pointer_mode */ +/* 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(); +}*/ /** * Returns the MouseData associated with the nth input device's pointer. This @@ -350,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; } @@ -377,70 +390,6 @@ close_ime() { return; } -/** - * 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; -} - -/** - * 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; -} - -/** - * 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; -} - -/** - * 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(), nullptr); - nassertr(_input_devices[device].has_pointer_event(), nullptr); - result = _input_devices[device].get_pointer_events(); - } - return result; -} - /** * Determines which of the indicated window sizes are supported by available * hardware (e.g. in fullscreen mode). @@ -739,11 +688,10 @@ system_changed_size(int x_size, int y_size) { * 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); return index; } diff --git a/panda/src/display/graphicsWindow.h b/panda/src/display/graphicsWindow.h index c9c9900d3e..074aa8a41f 100644 --- a/panda/src/display/graphicsWindow.h +++ b/panda/src/display/graphicsWindow.h @@ -82,7 +82,9 @@ PUBLISHED: // Mouse and keyboard routines int get_num_input_devices() const; + InputDevice *get_input_device(int i) const; std::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; @@ -90,20 +92,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);*/ virtual 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(){}; @@ -143,8 +139,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 446dca6ab6..d313af2c82 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -12,213 +12,45 @@ */ /** - * + * Returns the PointerData associated with the input device's pointer. This + * only makes sense if has_pointer() also returns true. */ -INLINE GraphicsWindowInputDevice:: -GraphicsWindowInputDevice() { - LightMutexHolder holder(_lock); - _flags = 0; -} - -/** - * - */ -INLINE std::string GraphicsWindowInputDevice:: -get_name() const { - LightMutexHolder holder(_lock); - return _name; -} - -/** - * - */ -INLINE bool GraphicsWindowInputDevice:: -has_pointer() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_pointer) != 0); -} - -/** - * - */ -INLINE bool GraphicsWindowInputDevice:: -has_keyboard() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_keyboard) != 0); -} - -/** - * Returns the MouseData associated with the input device's pointer. - */ -INLINE MouseData GraphicsWindowInputDevice:: +INLINE PointerData GraphicsWindowInputDevice:: get_pointer() const { LightMutexHolder holder(_lock); - return _mouse_data; -} - -/** - * 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; -} - -/** - * 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) { - LightMutexHolder holder(_lock); - _device_index = index; -} - -/** - * Enables the generation of mouse-movement events. - */ -INLINE void GraphicsWindowInputDevice:: -enable_pointer_events() { - LightMutexHolder holder(_lock); - _enable_pointer_events = true; -} - -/** - * Disables the generation of mouse-movement events. - */ -INLINE void GraphicsWindowInputDevice:: -disable_pointer_events() { - LightMutexHolder holder(_lock); - _enable_pointer_events = false; - _pointer_events.clear(); -} - -/** - * 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()); -} - -/** - * 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()); -} - -/** - * 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()); -} - -/** - * Records that the indicated keystroke has been generated. - */ -INLINE void GraphicsWindowInputDevice:: -keystroke(int keycode) { - keystroke(keycode, ClockObject::get_global_clock()->get_frame_time()); -} - -/** - * 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()); -} - -/** - * 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()); -} - -/** - * 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()); + if (!_pointers.empty()) { + return _pointers[0]; + } else { + return PointerData(); + } } /** * To be called by a particular kind of GraphicsWindow to indicate that the - * pointer is within the window, at the given pixel coordinates. + * pointer data has changed. */ 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()); +update_pointer(PointerData data, double time) { + LightMutexHolder holder(_lock); + InputDevice::update_pointer(std::move(data), time); } /** * To be called by a particular kind of GraphicsWindow to indicate that the - * pointer is no longer within the window. + * pointer has moved by the given relative amount. */ 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()); +pointer_moved(double x, double y, double time) { + LightMutexHolder holder(_lock); + InputDevice::pointer_moved(0, x, y, time); } /** * To be called by a particular kind of GraphicsWindow to indicate that the - * pointer is within the window, at the given pixel coordinates. + * pointer no longer exists. */ 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); -} - -/** - * 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(double time) { - // mutex is handled in set pointer .. convience function - set_pointer(false, _mouse_data._xpos, _mouse_data._ypos, time); -} - -/** - * - */ -INLINE bool GraphicsWindowInputDevice:: -operator == (const GraphicsWindowInputDevice &) const { - return true; -} - -/** - * - */ -INLINE bool GraphicsWindowInputDevice:: -operator != (const GraphicsWindowInputDevice &) const { - return false; -} - -/** - * - */ -INLINE bool GraphicsWindowInputDevice:: -operator < (const GraphicsWindowInputDevice &) const { - return false; +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 377f3986e4..04bac6d81d 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -16,12 +16,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" +TypeHandle GraphicsWindowInputDevice::_type_handle; using std::string; @@ -34,206 +29,43 @@ using std::string; * below. */ 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) +GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, bool pointer, bool keyboard) : + InputDevice(name, DeviceClass::virtual_device) { + if (pointer) { + enable_feature(Feature::pointer); + add_pointer(PointerType::mouse, 0); + } + if (keyboard) { + enable_feature(Feature::keyboard); + } } /** * 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, true, false); } /** * 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, false, true); } /** * 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 - GraphicsWindowInputDevice(host, name, IDF_has_pointer | IDF_has_keyboard); -} - -/** - * - */ -GraphicsWindowInputDevice:: -GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) -{ - *this = copy; -} - -/** - * - */ -void GraphicsWindowInputDevice:: -operator = (const GraphicsWindowInputDevice ©) -{ - LightMutexHolder holder(_lock); - LightMutexHolder holder1(copy._lock); - _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; -} - -/** - * - */ -GraphicsWindowInputDevice:: -~GraphicsWindowInputDevice() { -} - -/** - * 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(); -} - -/** - * 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; -} - -/** - * 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 != nullptr); -} - -/** - * 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 = nullptr; - return result; -} - -/** - * 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; -} - -/** - * 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; -} - -/** - * 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 == nullptr) { - _pointer_events = new PointerEventList(); - } - _pointer_events->add_event(_mouse_data._in_window, - _mouse_data._xpos, - _mouse_data._ypos, - seq, time); - } + return new GraphicsWindowInputDevice(host, name, true, true); } /** @@ -242,7 +74,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); } @@ -254,7 +86,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); } @@ -264,7 +96,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); } @@ -274,7 +106,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)); } /** @@ -286,7 +118,7 @@ void GraphicsWindowInputDevice:: candidate(const std::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)); } @@ -303,7 +135,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(); } @@ -314,7 +146,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)); } /** @@ -323,5 +155,47 @@ 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)); +} + +/** + * 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 b570b5f384..22317a1ab0 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -15,131 +15,73 @@ #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; /** - * 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. + * 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 std::string &name, int flags); + GraphicsWindowInputDevice(GraphicsWindow *host, const std::string &name, + bool pointer, bool keyboard); public: - static GraphicsWindowInputDevice pointer_only(GraphicsWindow *host, const std::string &name); - static GraphicsWindowInputDevice keyboard_only(GraphicsWindow *host, const std::string &name); - static GraphicsWindowInputDevice pointer_and_keyboard(GraphicsWindow *host, const std::string &name); + 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(const GraphicsWindowInputDevice ©); - void operator = (const GraphicsWindowInputDevice ©); - ~GraphicsWindowInputDevice(); - - INLINE std::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(); + GraphicsWindowInputDevice() = default; 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 std::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 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: - enum InputDeviceFlags { - IDF_has_pointer = 0x01, - IDF_has_keyboard = 0x02 - }; - typedef pdeque ButtonEvents; - - LightMutex _lock; - - GraphicsWindow *_host; - - std::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" - #endif diff --git a/panda/src/device/mouseAndKeyboard.cxx b/panda/src/display/mouseAndKeyboard.cxx similarity index 77% rename from panda/src/device/mouseAndKeyboard.cxx rename to panda/src/display/mouseAndKeyboard.cxx index 4a8e599afe..b1eb49027d 100644 --- a/panda/src/device/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,7 +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)); - _button_events = new ButtonEventList; + + _device->enable_pointer_events(); } /** @@ -48,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,17 +65,15 @@ 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)); + + GraphicsWindowInputDevice *device = (GraphicsWindowInputDevice *)_device.p(); + + 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)); } @@ -101,8 +86,8 @@ 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()) { + PointerData mdata = device->get_pointer(); if (mdata._in_window) { // Get mouse motion in pixels. diff --git a/panda/src/device/mouseAndKeyboard.h b/panda/src/display/mouseAndKeyboard.h similarity index 94% rename from panda/src/device/mouseAndKeyboard.h rename to panda/src/display/mouseAndKeyboard.h index 15f7544fb8..66cad13dc0 100644 --- a/panda/src/device/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, @@ -63,10 +60,9 @@ private: PT(EventStoreVec2) _pixel_xy; PT(EventStoreVec2) _pixel_size; PT(EventStoreVec2) _xy; - PT(ButtonEventList) _button_events; PT(GraphicsWindow) _window; - int _device; + PT(InputDevice) _device; public: static TypeHandle get_class_type() { diff --git a/panda/src/display/p3display_composite2.cxx b/panda/src/display/p3display_composite2.cxx index b9d33ab445..ef13357435 100644 --- a/panda/src/display/p3display_composite2.cxx +++ b/panda/src/display/p3display_composite2.cxx @@ -4,6 +4,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/display/subprocessWindow.cxx b/panda/src/display/subprocessWindow.cxx index 5ea85c3cc9..21e4b634c2 100644 --- a/panda/src/display/subprocessWindow.cxx +++ b/panda/src/display/subprocessWindow.cxx @@ -39,9 +39,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. @@ -83,9 +82,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; @@ -112,14 +111,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); } } } @@ -556,9 +555,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 1339b8f90d..0a887d9e8e 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; diff --git a/panda/src/egldisplay/eglGraphicsWindow.cxx b/panda/src/egldisplay/eglGraphicsWindow.cxx index efb550c728..89deb47deb 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 || (size_t)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 7647605fd3..3d4ef62e44 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/event/buttonEventList.h b/panda/src/event/buttonEventList.h index c9d9479490..0a7822fc63 100644 --- a/panda/src/event/buttonEventList.h +++ b/panda/src/event/buttonEventList.h @@ -31,7 +31,7 @@ class DatagramIterator; * but it may be used anywhere a list of ButtonEvents 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/event/pointerEvent.I b/panda/src/event/pointerEvent.I index 1321efe477..003cbf5df0 100644 --- a/panda/src/event/pointerEvent.I +++ b/panda/src/event/pointerEvent.I @@ -11,59 +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) -{ -} - -/** - * - */ -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 d98cc99989..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,10 +25,7 @@ class DatagramIterator; */ class EXPCL_PANDA_EVENT PointerEvent { public: - - INLINE PointerEvent(); - INLINE PointerEvent(const PointerEvent ©); - INLINE void operator = (const PointerEvent ©); + PointerEvent() = default; INLINE bool operator == (const PointerEvent &other) const; INLINE bool operator != (const PointerEvent &other) const; @@ -40,16 +37,19 @@ public: void read_datagram(DatagramIterator &scan); public: - bool _in_window; - int _xpos; - int _ypos; - int _dx; - int _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 eda98b6473..8309609832 100644 --- a/panda/src/event/pointerEventList.I +++ b/panda/src/event/pointerEventList.I @@ -38,99 +38,115 @@ 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(); } +/** + * 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. */ 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 4c9d8472c2..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. @@ -110,6 +146,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 b9daf9ca4d..e9ae1c9610 100644 --- a/panda/src/event/pointerEventList.h +++ b/panda/src/event/pointerEventList.h @@ -34,21 +34,24 @@ 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(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); bool encircles(int x, int y) const; double total_turns(double sec) const; @@ -58,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/config_putil.cxx b/panda/src/putil/config_putil.cxx index 593da51fd0..93694b3d30 100644 --- a/panda/src/putil/config_putil.cxx +++ b/panda/src/putil/config_putil.cxx @@ -31,6 +31,7 @@ #include "datagram.h" #include "doubleBitMask.h" #include "factoryParam.h" +#include "gamepadButton.h" #include "namable.h" #include "nodeCachedReferenceCount.h" #include "paramValue.h" @@ -238,6 +239,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..2924f0bf90 --- /dev/null +++ b/panda/src/putil/gamepadButton.cxx @@ -0,0 +1,118 @@ +/** + * 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 gamepadButton.cxx + * @author rdb + * @date 2015-08-21 + */ + +#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(next) +DEFINE_GAMEPAD_BUTTON_HANDLE(previous) + +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(face_1) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_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 ((size_t)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 + * 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(_next, "next"); + ButtonRegistry::ptr()->register_button(_previous, "previous"); + + 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(_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"); + 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 new file mode 100644 index 0000000000..774ee8ef2f --- /dev/null +++ b/panda/src/putil/gamepadButton.h @@ -0,0 +1,68 @@ +/** + * 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 gamepadButton.h + * @author rdb + * @date 2015-08-21 + */ + +#ifndef GAMEPADBUTTON_H +#define GAMEPADBUTTON_H + +#include "pandabase.h" + +#include "buttonHandle.h" + +/** + * 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 next(); + static ButtonHandle previous(); + + 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 face_1(); + static ButtonHandle face_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(); +}; + +#endif diff --git a/panda/src/putil/mouseData.I b/panda/src/putil/mouseData.I deleted file mode 100644 index c0ab6997ef..0000000000 --- a/panda/src/putil/mouseData.I +++ /dev/null @@ -1,73 +0,0 @@ -/** - * 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 mouseData.I - * @author drose - * @date 2002-07-15 - */ - -/** - * - */ -INLINE MouseData:: -MouseData() { - _in_window = false; - _xpos = 0; - _ypos = 0; -} - -/** - * - */ -INLINE MouseData:: -MouseData(const MouseData ©) : - _in_window(copy._in_window), - _xpos(copy._xpos), - _ypos(copy._ypos) -{ -} - -/** - * - */ -INLINE void MouseData:: -operator = (const MouseData ©) { - _in_window = copy._in_window; - _xpos = copy._xpos; - _ypos = copy._ypos; -} - -/** - * - */ -INLINE double MouseData:: -get_x() const { - return _xpos; -} - -/** - * - */ -INLINE double MouseData:: -get_y() const { - return _ypos; -} - -/** - * - */ -INLINE bool MouseData:: -get_in_window() const { - return _in_window; -} - - -INLINE std::ostream &operator << (std::ostream &out, const MouseData &md) { - md.output(out); - return out; -} 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_composite1.cxx b/panda/src/putil/p3putil_composite1.cxx index 446f7f1f07..f78459eff7 100644 --- a/panda/src/putil/p3putil_composite1.cxx +++ b/panda/src/putil/p3putil_composite1.cxx @@ -29,4 +29,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..80ae7450bb 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" @@ -7,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/pointerData.I b/panda/src/putil/pointerData.I new file mode 100644 index 0000000000..db508d8e71 --- /dev/null +++ b/panda/src/putil/pointerData.I @@ -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 pointerData.I + * @author drose + * @date 2002-07-15 + */ + +/** + * + */ +INLINE double PointerData:: +get_x() const { + return _xpos; +} + +/** + * + */ +INLINE double PointerData:: +get_y() const { + return _ypos; +} + +/** + * 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 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. + */ +INLINE double PointerData:: +get_pressure() const { + return _pressure; +} + + +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..30580cbd6a --- /dev/null +++ b/panda/src/putil/pointerData.h @@ -0,0 +1,72 @@ +/** + * 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" + +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. + */ +class EXPCL_PANDA_PUTIL PointerData { +PUBLISHED: + INLINE double get_x() const; + INLINE double get_y() const; + INLINE bool get_in_window() const; + +public: + INLINE int get_id() const; + INLINE PointerType get_type() 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(type, get_type); + MAKE_PROPERTY(id, get_id); + MAKE_PROPERTY(in_window, get_in_window); + MAKE_PROPERTY(pressure, get_pressure); + +public: + bool _in_window = false; + double _xpos = 0.0; + double _ypos = 0.0; + double _pressure = 0.0; + PointerType _type = PointerType::unknown; + int _id = 0; +}; + +INLINE std::ostream &operator << (std::ostream &out, const PointerData &md); + +#include "pointerData.I" + +#endif 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/tinydisplay/tinyOsxGraphicsWindow.mm b/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm index 01892094b0..d1df628415 100644 --- a/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm +++ b/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm @@ -397,7 +397,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); } @@ -466,10 +466,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; @@ -545,7 +545,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) { @@ -563,7 +563,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); @@ -1253,19 +1253,19 @@ void TinyOsxGraphicsWindow::SystemPointToLocalPoint(Point &qdGlobalPoint) { SystemPointToLocalPoint(qdGlobalPoint); if (wheelAxis == kEventMouseWheelAxisY) { - set_pointer_in_window((int)qdGlobalPoint.h, (int)qdGlobalPoint.v); - _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()); - _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()); - _wheel_delta += wheel_scale; - } + set_pointer_in_window((int)qdGlobalPoint.h, (int)qdGlobalPoint.v); + _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()); + _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()); + _wheel_delta += wheel_scale; + } } result = noErr; break; @@ -1438,25 +1438,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()); } } @@ -1657,7 +1657,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 640adf3d5d..9c221846eb 100644 --- a/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx +++ b/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx @@ -43,9 +43,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")); } /** @@ -168,35 +166,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 e84fa4d834..1e71324baf 100644 --- a/panda/src/tinydisplay/tinyXGraphicsWindow.cxx +++ b/panda/src/tinydisplay/tinyXGraphicsWindow.cxx @@ -195,8 +195,6 @@ process_events() { return; } - poll_raw_mice(); - XEvent event; XKeyEvent keyrelease_event; bool got_keyrelease_event = false; @@ -271,18 +269,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: @@ -299,11 +297,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: @@ -312,7 +310,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 d0d1b773ca..2745aab75e 100644 --- a/panda/src/vrpn/vrpnAnalog.cxx +++ b/panda/src/vrpn/vrpnAnalog.cxx @@ -99,16 +99,8 @@ 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]) { - 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]); } - device->unlock(); } } diff --git a/panda/src/vrpn/vrpnButton.cxx b/panda/src/vrpn/vrpnButton.cxx index 71af239f88..3312afa31d 100644 --- a/panda/src/vrpn/vrpnButton.cxx +++ b/panda/src/vrpn/vrpnButton.cxx @@ -103,8 +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->acquire(); - device->set_button_state(info.button, info.state != 0); - device->unlock(); + device->button_changed(info.button, info.state != 0); } } diff --git a/panda/src/vrpn/vrpnDial.cxx b/panda/src/vrpn/vrpnDial.cxx index 5c06f797ee..62b1fe73df 100644 --- a/panda/src/vrpn/vrpnDial.cxx +++ b/panda/src/vrpn/vrpnDial.cxx @@ -104,8 +104,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 6e4e196769..e1b08e2a72 100644 --- a/panda/src/vrpn/vrpnTracker.cxx +++ b/panda/src/vrpn/vrpnTracker.cxx @@ -107,11 +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->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->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)); } } } @@ -133,13 +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->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->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)); } } } @@ -161,13 +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->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->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)); } } } diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 703aba0149..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; @@ -138,7 +138,7 @@ get_pointer(int device) const { double time = ClockObject::get_global_clock()->get_real_time(); result._xpos = cpos.x; result._ypos = cpos.y; - ((GraphicsWindowInputDevice &)_input_devices[0]).set_pointer(result._in_window, result._xpos, result._ypos, time); + ((GraphicsWindowInputDevice &)_input_devices[0]).set_pointer_in_window(result._xpos, result._ypos, time); } } return result; @@ -164,7 +164,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. @@ -175,14 +175,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; } } @@ -626,7 +626,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; @@ -636,9 +635,10 @@ initialize_input_devices() { // Clear the handle array, and set up the system keyboardmouse 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; // Get the number of devices. if (GetRawInputDeviceList(nullptr, &nInputDevices, sizeof(RAWINPUTDEVICELIST)) != 0) { @@ -679,14 +679,15 @@ 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); + + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_only(this, psName); + device->set_pointer_in_window(0, 0); add_input_device(device); } } @@ -1547,8 +1548,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(); @@ -1559,8 +1560,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; @@ -1570,8 +1571,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; @@ -1583,11 +1584,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; @@ -1597,7 +1598,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: @@ -1605,7 +1606,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: @@ -1613,7 +1614,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: @@ -1624,9 +1625,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; @@ -1746,7 +1747,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; @@ -1777,7 +1778,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]); } } @@ -1790,10 +1791,10 @@ 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), - std::min(cursor_pos, delta_start), - std::max(cursor_pos, delta_start), - cursor_pos); + _input->candidate(wstring(ime_buffer, num_chars), + std::min(cursor_pos, delta_start), + std::max(cursor_pos, delta_start), + cursor_pos); } ImmReleaseContext(hwnd, hIMC); } @@ -1811,7 +1812,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // 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; @@ -1937,8 +1938,8 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { lptstr = (char *) GlobalLock(hglb); if (lptstr != nullptr) { char *pChar; - for (pChar=lptstr; *pChar; pChar++) { - _input_devices[0].keystroke((uchar)*pChar); + for (pChar = lptstr; *pChar; pChar++) { + _input->keystroke((uchar)*pChar); } GlobalUnlock(hglb); } @@ -2059,7 +2060,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; @@ -2371,9 +2372,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); } } @@ -2384,7 +2385,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); } } @@ -2394,7 +2395,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); } } @@ -2404,7 +2405,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); } } @@ -2414,7 +2415,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); } } @@ -2721,48 +2722,49 @@ handle_raw_input(HRAWINPUT hraw) { 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()); } } } @@ -2773,7 +2775,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; } @@ -2783,7 +2785,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 5b0bb208c1..841e2b9854 100644 --- a/panda/src/windisplay/winGraphicsWindow.h +++ b/panda/src/windisplay/winGraphicsWindow.h @@ -189,6 +189,8 @@ private: bool _lalt_down; bool _ralt_down; + GraphicsWindowInputDevice *_input; + // following adds support platform specfic window processing functions. typedef pset WinProcClasses; WinProcClasses _window_proc_classes; diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 9a1555c27e..ef86c82166 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -29,14 +29,10 @@ #include "get_x11.h" #include "pnmImage.h" #include "pnmFileTypeRegistry.h" +#include "evdevInputDevice.h" -#include -#include #include - -#ifdef PHAVE_LINUX_INPUT_H -#include -#endif +#include using std::istream; using std::ostringstream; @@ -90,8 +86,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))) - /** * */ @@ -126,9 +120,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; } /** @@ -156,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. @@ -192,29 +186,24 @@ 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_devices[0].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_devices[0].get_pointer(); if (!md.get_in_window() || md.get_x() != x || md.get_y() != y) { if (!_dga_mouse_enabled) { LightReMutexHolder holder(x11GraphicsPipe::_x_mutex); 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 || (size_t)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; } } @@ -309,8 +298,6 @@ process_events() { return; } - poll_raw_mice(); - XEvent event; XKeyEvent keyrelease_event; bool got_keyrelease_event = false; @@ -369,25 +356,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); + 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_devices[0].set_pointer_in_window(event.xmotion.x, event.xmotion.y); + _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y); } break; @@ -406,15 +393,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()); + PointerData 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: @@ -423,7 +410,7 @@ process_events() { break; case FocusOut: - _input_devices[0].focus_lost(); + _input->focus_lost(); properties.set_foreground(false); changed_properties = true; break; @@ -812,7 +799,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() @@ -1330,6 +1317,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() { @@ -1338,66 +1326,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(nullptr, 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"; } @@ -1407,72 +1372,13 @@ open_raw_mice() { #endif } -/** - * Reads events from the raw mouse device files. - */ -void x11GraphicsWindow:: -poll_raw_mice() { -#ifdef PHAVE_LINUX_INPUT_H - for (MouseDeviceInfo &inf : _mouse_device_info) { - // 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 -} - /** * Generates a keystroke corresponding to the indicated X KeyPress event. */ 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) { @@ -1489,14 +1395,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()); } } } @@ -1507,30 +1413,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); + } } } @@ -1540,30 +1448,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); + } } } @@ -1644,7 +1554,7 @@ get_button(XKeyEvent &key_event, bool allow_shift) { // 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); } } @@ -1985,117 +1895,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 906a64b623..f512b16a19 100644 --- a/panda/src/x11display/x11GraphicsWindow.h +++ b/panda/src/x11display/x11GraphicsWindow.h @@ -70,7 +70,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); @@ -88,19 +87,14 @@ protected: LVecBase2i _fixed_size; + GraphicsWindowInputDevice *_input; + long _event_mask; bool _awaiting_configure; bool _dga_mouse_enabled; Bool _override_redirect; Atom _wm_delete_window; - struct MouseDeviceInfo { - int _fd; - int _input_device_index; - std::string _io_buffer; - }; - pvector _mouse_device_info; - x11GraphicsPipe::pfn_XRRGetScreenInfo _XRRGetScreenInfo; x11GraphicsPipe::pfn_XRRSetScreenConfig _XRRSetScreenConfig; 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() diff --git a/samples/gamepad/flightstick.py b/samples/gamepad/flightstick.py new file mode 100644 index 0000000000..4520cfefb2 --- /dev/null +++ b/samples/gamepad/flightstick.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +''' +Demonstrate usage of flight stick + +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 +from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 +from direct.gui.OnscreenText import OnscreenText + +loadPrcFileData("", """ + default-fov 60 + notify-level-device debug +""") + +STICK_DEAD_ZONE = 0.02 +THROTTLE_DEAD_ZONE = 0.02 + + +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.DeviceClass.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-trigger", self.action, extraArgs=["Trigger"]) + self.accept("flight_stick0-trigger-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 + 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.DeviceClass.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.DeviceClass.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 + + # 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 + self.currentMoveSpeed += dt * accleration + + # Steering + + # Control the cameras yaw/Headding + stick_yaw = self.flightStick.findAxis(InputDevice.Axis.yaw) + 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) + 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) + 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() +app.run() diff --git a/samples/gamepad/gamepad.py b/samples/gamepad/gamepad.py new file mode 100644 index 0000000000..8679a74187 --- /dev/null +++ b/samples/gamepad/gamepad.py @@ -0,0 +1,195 @@ +#!/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 panda3d.core import TextPropertiesManager +from direct.gui.OnscreenText import OnscreenText + +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() + + # 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.lblAction = OnscreenText( + text = "Action", + fg=(1,1,1,1), + scale = .15) + self.lblAction.hide() + + # Is there a gamepad connected? + self.gamepad = None + devices = self.devices.getDevices(InputDevice.DeviceClass.gamepad) + if devices: + self.connect(devices[0]) + + # Accept device dis-/connection events + self.accept("connect-device", self.connect) + self.accept("disconnect-device", self.disconnect) + + self.accept("escape", exit) + + # Accept button events of the first connected gamepad + 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 the default mouse-camera controls since we need to handle + # our own camera controls. + 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 gamepad and we don't have a + # gamepad yet. + if device.device_class == InputDevice.DeviceClass.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): + """Event handler that is called when a device is removed.""" + + 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.DeviceClass.gamepad) + 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 \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() + + 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 + 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 + + # 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 + +app = App() +app.run() diff --git a/samples/gamepad/mappingGUI.py b/samples/gamepad/mappingGUI.py new file mode 100644 index 0000000000..17896c33a6 --- /dev/null +++ b/samples/gamepad/mappingGUI.py @@ -0,0 +1,401 @@ +#!/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, + GamepadButton, + KeyboardButton) + +# Make sure the textures look crisp on every device that supports +# non-power-2 textures +loadPrcFileData("", "textures-auto-power-2 #t") + +# 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): + self.__map = dict.fromkeys(self.actions) + + def mapButton(self, action, button): + self.__map[action] = ("button", str(button)) + + def mapAxis(self, action, axis): + self.__map[action] = ("axis", axis.name) + + def unmap(self): + self.__map[action] = None + + 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 + + self.attachedDevices = [] + + # Initialize the DirectGUI stuff. + self.dialog = 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=button_geom, + 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.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 + 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") + 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.mapping.actions)*0.1), 0.09) + self.lstActionMap.setCanvasSize() + + 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 + 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.actionLabels[action]["text"] = self.mapping.formatMapping(action) + + # cleanup + 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") + + # Now detach all the input devices. + for device in self.attachedDevices: + base.detachInputDevice(device) + self.attachedDevices.clear() + + 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.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 devices: + for axis in device.axes: + if device not in self.axisStates.keys(): + self.axisStates.update({device: {axis.axis: axis.value}}) + else: + 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 self.attachedDevices: + if device.device_class == InputDevice.DeviceClass.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.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 __makeListItem(self, action, event, index): + def dummy(): pass + if index % 2 == 0: + bg = self.listBGEven + else: + bg = self.listBGOdd + item = DirectFrame( + 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), + 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) + self.actionLabels[action] = lbl + + 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]) + btn.setTransparency(True) + btn.reparentTo(item) + return item + +app = MappingGUIDemo() +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 0000000000..fbd06a4df3 Binary files /dev/null and b/samples/gamepad/models/click.png differ diff --git a/samples/gamepad/models/dec_click.png b/samples/gamepad/models/dec_click.png new file mode 100644 index 0000000000..4c28814535 Binary files /dev/null and b/samples/gamepad/models/dec_click.png differ diff --git a/samples/gamepad/models/dec_disabled.png b/samples/gamepad/models/dec_disabled.png new file mode 100644 index 0000000000..d1978f241f Binary files /dev/null and b/samples/gamepad/models/dec_disabled.png differ diff --git a/samples/gamepad/models/dec_hover.png b/samples/gamepad/models/dec_hover.png new file mode 100644 index 0000000000..87f1a267b5 Binary files /dev/null and b/samples/gamepad/models/dec_hover.png differ 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 0000000000..a097c1e5cb Binary files /dev/null and b/samples/gamepad/models/dec_ready.png differ diff --git a/samples/gamepad/models/dialog.png b/samples/gamepad/models/dialog.png new file mode 100644 index 0000000000..7ceb2c1809 Binary files /dev/null and b/samples/gamepad/models/dialog.png differ diff --git a/samples/gamepad/models/disabled.png b/samples/gamepad/models/disabled.png new file mode 100644 index 0000000000..9b9dfe5014 Binary files /dev/null and b/samples/gamepad/models/disabled.png differ diff --git a/samples/gamepad/models/hover.png b/samples/gamepad/models/hover.png new file mode 100644 index 0000000000..f77986b296 Binary files /dev/null and b/samples/gamepad/models/hover.png differ diff --git a/samples/gamepad/models/inc_click.png b/samples/gamepad/models/inc_click.png new file mode 100644 index 0000000000..7afed6b761 Binary files /dev/null and b/samples/gamepad/models/inc_click.png differ diff --git a/samples/gamepad/models/inc_disabled.png b/samples/gamepad/models/inc_disabled.png new file mode 100644 index 0000000000..83817cc587 Binary files /dev/null and b/samples/gamepad/models/inc_disabled.png differ diff --git a/samples/gamepad/models/inc_hover.png b/samples/gamepad/models/inc_hover.png new file mode 100644 index 0000000000..089df4352e Binary files /dev/null and b/samples/gamepad/models/inc_hover.png differ 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 0000000000..9d4c153e44 Binary files /dev/null and b/samples/gamepad/models/inc_ready.png differ diff --git a/samples/gamepad/models/li_ready_even.png b/samples/gamepad/models/li_ready_even.png new file mode 100644 index 0000000000..87736e8570 Binary files /dev/null and b/samples/gamepad/models/li_ready_even.png differ diff --git a/samples/gamepad/models/li_ready_odd.png b/samples/gamepad/models/li_ready_odd.png new file mode 100644 index 0000000000..dcc22f71a2 Binary files /dev/null and b/samples/gamepad/models/li_ready_odd.png differ diff --git a/samples/gamepad/models/list_item_even.egg b/samples/gamepad/models/list_item_even.egg new file mode 100644 index 0000000000..d4e747a2a0 --- /dev/null +++ b/samples/gamepad/models/list_item_even.egg @@ -0,0 +1,33 @@ + { + "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 0000000000..e9cfed43da Binary files /dev/null and b/samples/gamepad/models/ready.png differ diff --git a/samples/gamepad/models/thumb_click.png b/samples/gamepad/models/thumb_click.png new file mode 100644 index 0000000000..2fc059da82 Binary files /dev/null and b/samples/gamepad/models/thumb_click.png differ diff --git a/samples/gamepad/models/thumb_disabled.png b/samples/gamepad/models/thumb_disabled.png new file mode 100644 index 0000000000..8ad2805f10 Binary files /dev/null and b/samples/gamepad/models/thumb_disabled.png differ diff --git a/samples/gamepad/models/thumb_hover.png b/samples/gamepad/models/thumb_hover.png new file mode 100644 index 0000000000..258392f3b1 Binary files /dev/null and b/samples/gamepad/models/thumb_hover.png differ 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 0000000000..36a41ae599 Binary files /dev/null and b/samples/gamepad/models/thumb_ready.png differ 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 0000000000..e8fd66b487 Binary files /dev/null and b/samples/gamepad/models/xbone-icons.png differ diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py new file mode 100644 index 0000000000..09b0d9d167 --- /dev/null +++ b/samples/gamepad/steeringWheel.py @@ -0,0 +1,167 @@ +#!/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 steering wheel connected? + self.wheel = None + devices = self.devices.getDevices(InputDevice.DeviceClass.steering_wheel) + 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) + + # Accept button events of the first connected steering wheel + 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) + + # 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 + 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() + 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 steering wheel and we don't have a + # wheel yet. + if device.device_class == InputDevice.DeviceClass.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): + """Event handler that is called when a device is removed.""" + + 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.DeviceClass.steering_wheel) + 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, 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() + + if not self.wheel: + 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 + 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 = 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 - self.wheel.findAxis(InputDevice.Axis.wheel).value + 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()