Merge branch 'master' into deploy-ng

This commit is contained in:
rdb 2018-12-01 22:14:01 +01:00
commit c560c502b9
139 changed files with 10222 additions and 1912 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -679,6 +679,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")
@ -2504,6 +2505,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:
@ -3964,6 +3966,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/
#
@ -4016,34 +4046,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/
#
@ -4208,7 +4210,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')

View File

@ -93,8 +93,8 @@ BulletCapsuleShape *BulletCapsuleShape::
make_from_solid(const CollisionTube *solid) {
PN_stdfloat radius = solid->get_radius();
// CollisionTube height includes the hemispheres, Bullet only wants the cylinder height.
PN_stdfloat height = (solid->get_point_b() - solid->get_point_a()).length() - (radius * 2);
// Get tube's cylinder height: length from point A to point B
PN_stdfloat height = (solid->get_point_b() - solid->get_point_a()).length();
// CollisionTubes are always Z-Up.
return new BulletCapsuleShape(radius, height, Z_up);

View File

@ -89,6 +89,7 @@ private:
NSUInteger _modifier_keys;
UInt32 _dead_key_state;
CGDirectDisplayID _display;
PT(GraphicsWindowInputDevice) _input;
bool _mouse_hidden;
bool _context_needs_update;

View File

@ -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());
}
}

View File

@ -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);
}
/**

View File

@ -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));
}

View File

@ -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

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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<AnalogState> Controls;
Controls _controls;
public:
static TypeHandle get_class_type() {

View File

@ -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;

View File

@ -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;
}

View File

@ -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";
}
}

View File

@ -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<ButtonState> 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"

View File

@ -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
}

View File

@ -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();
}
/**

View File

@ -15,12 +15,7 @@
#define CLIENTDEVICE_H
#include "pandabase.h"
#include "typedReferenceCount.h"
#ifdef OLD_HAVE_IPC
#include <ipc_mutex.h>
#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();

View File

@ -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);
}

View File

@ -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;

View File

@ -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
}

View File

@ -17,6 +17,7 @@
#include "pandabase.h"
#include "notifyCategoryProxy.h"
#include "configVariableBool.h"
#include "configVariableInt.h"
NotifyCategoryDecl(device, EXPCL_PANDA_DEVICE, EXPTP_PANDA_DEVICE);

View File

@ -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);
}

View File

@ -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
*/

View File

@ -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 <fcntl.h>
#include <linux/input.h>
#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

View File

@ -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<int> _axis_indices;
pvector<int> _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

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 &copy) = delete;
InputDevice &operator = (const InputDevice &copy) = 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<ButtonEvent> 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<PointerData> Pointers;
Pointers _pointers;
PUBLISHED:
typedef pvector<ButtonState> Buttons;
typedef pvector<AxisState> 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

View File

@ -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;
}
}

View File

@ -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() {
}

View File

@ -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

View File

@ -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));
}
}

View File

@ -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<ButtonHandle, bool> _button_states;
pmap<InputDevice::Axis, double> _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

View File

@ -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();
}

View File

@ -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 &copy) :
_devices(copy._devices)
{
}
/**
*
*/
void InputDeviceSet::
operator = (const InputDeviceSet &copy) {
_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';
}
}

View File

@ -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 &copy);
void operator = (const InputDeviceSet &copy);
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<PT(InputDevice)> InputDevices;
InputDevices _devices;
};
INLINE std::ostream &operator << (std::ostream &out, const InputDeviceSet &col) {
col.output(out);
return out;
}
#include "inputDeviceSet.I"
#endif

View File

@ -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 <IOKit/hid/IOHIDElement.h>
#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__

View File

@ -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 <IOKit/hid/IOHIDDevice.h>
/**
* 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<IOHIDElementRef> _button_elements;
pvector<IOHIDElementRef> _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

View File

@ -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

View File

@ -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 <IOKit/hid/IOHIDManager.h>
/**
* 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

View File

@ -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 <dirent.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
/**
* 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<size_t> 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

View File

@ -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<InputDevice *> _evdev_devices;
friend class InputDeviceManager;
};
#endif // PHAVE_LINUX_INPUT_H
#endif

View File

@ -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
*/

View File

@ -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 <fcntl.h>
#include <linux/joystick.h>
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

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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.
*/

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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 <CfgMgr32.h>
#include <devpkey.h>
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<HANDLE, WinRawInputDevice *> _raw_devices;
pmap<std::string, WinRawInputDevice *> _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

View File

@ -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 <CfgMgr32.h>
#include <devpkey.h>
// 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

View File

@ -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<BitArray> _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<Index> _indices;
int _hat_data_index;
int _hat_data_minimum;
int _hat_left_button;
WinInputDeviceManager *_manager;
friend class WinInputDeviceManager;
};
#endif // _WIN32
#endif

View File

@ -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 <XInput.h>
#include <CfgMgr32.h>
#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

View File

@ -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 <CfgMgr32.h>
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

View File

@ -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));
}
/**

View File

@ -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:

View File

@ -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();

View File

@ -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;
}

View File

@ -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<PT(InputDevice)> InputDevices;
InputDevices _input_devices;
LightMutex _input_lock;

View File

@ -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);
}

View File

@ -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 &copy)
{
*this = copy;
}
/**
*
*/
void GraphicsWindowInputDevice::
operator = (const GraphicsWindowInputDevice &copy)
{
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);
}
}

View File

@ -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 &copy);
void operator = (const GraphicsWindowInputDevice &copy);
~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<GraphicsWindowInputDevice>. 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<ButtonEvent> 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<ButtonHandle> 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

View File

@ -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.

View File

@ -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() {

View File

@ -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"

View File

@ -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);
}
}

View File

@ -75,6 +75,7 @@ private:
private:
PT(GraphicsBuffer) _buffer;
PT(Texture) _texture;
PT(GraphicsWindowInputDevice) _input;
int _fd;
size_t _mmap_size;

View File

@ -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

View File

@ -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();

View File

@ -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 &copy);
INLINE void operator = (const ButtonEventList &copy);

View File

@ -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 &copy) :
_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 &copy) {
_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.
*/

View File

@ -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 &copy);
INLINE void operator = (const PointerEvent &copy);
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) {

View File

@ -38,99 +38,115 @@ operator = (const PointerEventList &copy) {
/**
* 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;
}
/**

View File

@ -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.
*/

View File

@ -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 &copy);
INLINE void operator = (const PointerEventList &copy);
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;

View File

@ -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();

View File

@ -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<ButtonHandle> 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");
}

View File

@ -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

View File

@ -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 &copy) :
_in_window(copy._in_window),
_xpos(copy._xpos),
_ypos(copy._ypos)
{
}
/**
*
*/
INLINE void MouseData::
operator = (const MouseData &copy) {
_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;
}

View File

@ -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 &copy);
INLINE void operator = (const MouseData &copy);
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

View File

@ -29,4 +29,3 @@
#include "factoryBase.cxx"
#include "factoryParam.cxx"
#include "factoryParams.cxx"
#include "globalPointerRegistry.cxx"

View File

@ -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"

View File

@ -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;
}

View File

@ -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 << ")";
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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:

View File

@ -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;

View File

@ -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();
}
}

Some files were not shown because too many files have changed in this diff Show More