Merge branch 'master' into deploy-ng
This commit is contained in:
commit
c560c502b9
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ private:
|
|||
NSUInteger _modifier_keys;
|
||||
UInt32 _dead_key_state;
|
||||
CGDirectDisplayID _display;
|
||||
PT(GraphicsWindowInputDevice) _input;
|
||||
bool _mouse_hidden;
|
||||
bool _context_needs_update;
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include "pandabase.h"
|
||||
#include "notifyCategoryProxy.h"
|
||||
#include "configVariableBool.h"
|
||||
#include "configVariableInt.h"
|
||||
|
||||
NotifyCategoryDecl(device, EXPCL_PANDA_DEVICE, EXPTP_PANDA_DEVICE);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file inputDevice.h
|
||||
* @author rdb
|
||||
* @date 2015-12-11
|
||||
*/
|
||||
|
||||
#ifndef INPUTDEVICE_H
|
||||
#define INPUTDEVICE_H
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
#include "buttonEvent.h"
|
||||
#include "buttonEventList.h"
|
||||
#include "pointerEvent.h"
|
||||
#include "pointerEventList.h"
|
||||
#include "pointerData.h"
|
||||
#include "trackerData.h"
|
||||
#include "clockObject.h"
|
||||
|
||||
#include "pdeque.h"
|
||||
#include "pvector.h"
|
||||
#include "lightMutex.h"
|
||||
#include "lightMutexHolder.h"
|
||||
|
||||
/**
|
||||
* This is a structure representing a single input device. Input devices may
|
||||
* have zero or more buttons, pointers, or axes associated with them, and
|
||||
* optionally a motion tracker.
|
||||
*
|
||||
* These devices are brought under a common interface because there is such a
|
||||
* large range of devices out there that may support any number of these types
|
||||
* of axes, we couldn't even begin to cover them with type-specific
|
||||
* subclasses.
|
||||
*
|
||||
* Use the various has_() and get_num_() methods to determine information about
|
||||
* the device capabilities. For instance, has_keyboard() will give an
|
||||
* indication that you can receive keystroke events from this device, and
|
||||
* get_num_buttons() will tell you that the device may send button events.
|
||||
*
|
||||
* There is the DeviceType enumeration, however, which will (if known) contain
|
||||
* identification of the general category of devices this fits in, such as
|
||||
* keyboard, mouse, gamepad, or flight stick.
|
||||
*/
|
||||
class EXPCL_PANDA_DEVICE InputDevice : public TypedReferenceCount {
|
||||
PUBLISHED:
|
||||
// This enum contains information that can be used to identify the
|
||||
// type of input device.
|
||||
enum class DeviceClass {
|
||||
// It is not known what type of device this is.
|
||||
unknown,
|
||||
|
||||
// This means that the device doesn't correspond to a physical
|
||||
// device, but rather to a dynamic source of input events.
|
||||
virtual_device,
|
||||
|
||||
// A physical, alphabetical keyboard.
|
||||
keyboard,
|
||||
|
||||
mouse,
|
||||
touch,
|
||||
|
||||
// A gamepad with action buttons, a D-pad, and thumbsticks.
|
||||
gamepad,
|
||||
|
||||
flight_stick,
|
||||
steering_wheel,
|
||||
dance_pad,
|
||||
|
||||
// Head-mounted display.
|
||||
hmd,
|
||||
|
||||
// 3D mouse, such as produced by 3Dconnexion.
|
||||
spatial_mouse,
|
||||
};
|
||||
|
||||
enum class Feature {
|
||||
// The device provides absolute screen coordinates.
|
||||
pointer,
|
||||
|
||||
// The device has an interface for providing text input.
|
||||
keyboard,
|
||||
|
||||
// The device has a motion tracker, such as an HMD.
|
||||
tracker,
|
||||
|
||||
// The device can produce force feedback.
|
||||
vibration,
|
||||
|
||||
// The device provides information about battery life.
|
||||
battery,
|
||||
};
|
||||
|
||||
enum class Axis {
|
||||
none,
|
||||
|
||||
// Generic translational axes
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
|
||||
// Generic rotational axes, used by joysticks and 3D mice
|
||||
yaw,
|
||||
pitch,
|
||||
roll,
|
||||
|
||||
// Gamepad
|
||||
left_x,
|
||||
left_y,
|
||||
left_trigger,
|
||||
right_x,
|
||||
right_y,
|
||||
right_trigger,
|
||||
|
||||
// Flight stick specific
|
||||
throttle,
|
||||
rudder, // When available separately from yaw
|
||||
|
||||
// Steering wheel / pedals
|
||||
wheel,
|
||||
accelerator,
|
||||
brake,
|
||||
};
|
||||
|
||||
enum State {
|
||||
S_unknown,
|
||||
S_up,
|
||||
S_down
|
||||
};
|
||||
|
||||
class ButtonState {
|
||||
public:
|
||||
constexpr ButtonState() = default;
|
||||
INLINE ButtonState(ButtonHandle handle);
|
||||
ALWAYS_INLINE bool is_known() const;
|
||||
ALWAYS_INLINE bool is_pressed() const;
|
||||
|
||||
PUBLISHED:
|
||||
operator bool() { return _state != S_unknown; }
|
||||
|
||||
MAKE_PROPERTY(known, is_known);
|
||||
MAKE_PROPERTY(pressed, is_pressed);
|
||||
|
||||
ButtonHandle handle = ButtonHandle::none();
|
||||
|
||||
public:
|
||||
State _state = S_unknown;
|
||||
};
|
||||
|
||||
class AxisState {
|
||||
public:
|
||||
constexpr AxisState() = default;
|
||||
|
||||
PUBLISHED:
|
||||
operator bool() { return known && value != 0.0; }
|
||||
|
||||
Axis axis = Axis::none;
|
||||
double value = 0.0;
|
||||
bool known = false;
|
||||
|
||||
public:
|
||||
double _scale = 1.0;
|
||||
double _bias = 0.0;
|
||||
};
|
||||
|
||||
class BatteryData {
|
||||
PUBLISHED:
|
||||
// Ranges from 0 through max_level.
|
||||
short level = -1;
|
||||
|
||||
// Maximum value of 'level' field.
|
||||
short max_level = -1;
|
||||
};
|
||||
|
||||
protected:
|
||||
InputDevice(const std::string &name, DeviceClass dev_class);
|
||||
|
||||
public:
|
||||
InputDevice();
|
||||
InputDevice(const InputDevice ©) = delete;
|
||||
InputDevice &operator = (const InputDevice ©) = delete;
|
||||
virtual ~InputDevice();
|
||||
|
||||
INLINE std::string get_name() const;
|
||||
INLINE std::string get_manufacturer() const;
|
||||
INLINE std::string get_serial_number() const;
|
||||
INLINE unsigned short get_vendor_id() const;
|
||||
INLINE unsigned short get_product_id() const;
|
||||
INLINE bool is_connected() const;
|
||||
INLINE DeviceClass get_device_class() const;
|
||||
|
||||
INLINE bool has_pointer() const;
|
||||
INLINE bool has_keyboard() const;
|
||||
INLINE bool has_tracker() const;
|
||||
INLINE bool has_vibration() const;
|
||||
INLINE bool has_battery() const;
|
||||
|
||||
INLINE TrackerData get_tracker() const;
|
||||
INLINE BatteryData get_battery() const;
|
||||
|
||||
INLINE size_t get_num_buttons() const;
|
||||
INLINE ButtonHandle get_button_map(size_t index) const;
|
||||
INLINE bool is_button_pressed(size_t index) const;
|
||||
INLINE bool is_button_known(size_t index) const;
|
||||
INLINE ButtonState get_button(size_t index) const;
|
||||
|
||||
INLINE size_t get_num_axes() const;
|
||||
INLINE double get_axis_value(size_t index) const;
|
||||
INLINE bool is_axis_known(size_t index) const;
|
||||
INLINE AxisState get_axis(size_t index) const;
|
||||
|
||||
PUBLISHED:
|
||||
// The human-readable name of this input device.
|
||||
MAKE_PROPERTY(name, get_name);
|
||||
|
||||
// The device's manufacturer, or the empty string if not known.
|
||||
MAKE_PROPERTY(manufacturer, get_manufacturer);
|
||||
|
||||
// The device's serial number, or the empty string if not known.
|
||||
MAKE_PROPERTY(serial_number, get_serial_number);
|
||||
|
||||
// USB vendor ID of the device, or 0 if not known.
|
||||
MAKE_PROPERTY(vendor_id, get_vendor_id);
|
||||
|
||||
// USB product ID of the device, or 0 if not known.
|
||||
MAKE_PROPERTY(product_id, get_product_id);
|
||||
|
||||
// This is false if we know that the device is not currently connected.
|
||||
// May report false positives if we can't know this with certainty.
|
||||
MAKE_PROPERTY(connected, is_connected);
|
||||
|
||||
// This contains an identification of the general type of device. If
|
||||
// this could not be determined, it is set to DC_unknown.
|
||||
MAKE_PROPERTY(device_class, get_device_class);
|
||||
|
||||
// Determine supported features
|
||||
INLINE bool has_feature(Feature feature) const;
|
||||
|
||||
// Getters for the various types of device data.
|
||||
MAKE_PROPERTY2(tracker, has_tracker, get_tracker);
|
||||
MAKE_PROPERTY2(battery, has_battery, get_battery);
|
||||
|
||||
// Make device buttons and axes iterable
|
||||
MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button);
|
||||
MAKE_SEQ_PROPERTY(axes, get_num_axes, get_axis);
|
||||
|
||||
// Associate buttons/axes with symbolic handles.
|
||||
INLINE void map_button(size_t index, ButtonHandle handle);
|
||||
INLINE void map_axis(size_t index, Axis axis);
|
||||
INLINE ButtonState find_button(ButtonHandle handle) const;
|
||||
INLINE AxisState find_axis(Axis axis) const;
|
||||
|
||||
// Enable rumble force-feedback effects
|
||||
INLINE void set_vibration(double strong, double weak);
|
||||
|
||||
INLINE void enable_pointer_events();
|
||||
INLINE void disable_pointer_events();
|
||||
|
||||
void poll();
|
||||
|
||||
bool has_button_event() const;
|
||||
PT(ButtonEventList) get_button_events();
|
||||
bool has_pointer_event() const;
|
||||
PT(PointerEventList) get_pointer_events();
|
||||
|
||||
virtual void output(std::ostream &out) const;
|
||||
|
||||
public:
|
||||
static std::string format_device_class(DeviceClass dc);
|
||||
static std::string format_axis(Axis axis);
|
||||
|
||||
protected:
|
||||
INLINE void enable_feature(Feature feature);
|
||||
|
||||
// Called during the constructor to add new axes or buttons
|
||||
int add_button(ButtonHandle handle);
|
||||
int add_axis(Axis axis, int minimum, int maximum, bool centered);
|
||||
int add_axis(Axis axis, int minimum, int maximum);
|
||||
|
||||
int add_pointer(PointerType type, int id, bool primary = false);
|
||||
void remove_pointer(int id);
|
||||
void update_pointer(PointerData data, double time);
|
||||
void pointer_moved(int id, double x, double y, double time);
|
||||
void button_changed(int index, bool down);
|
||||
void axis_changed(int index, int value);
|
||||
void set_axis_value(int index, double state);
|
||||
|
||||
void tracker_changed(const LPoint3 &pos, const LOrientation &orient, double time);
|
||||
|
||||
virtual void do_set_vibration(double low, double high);
|
||||
virtual void do_poll();
|
||||
|
||||
public:
|
||||
INLINE void set_connected(bool connected);
|
||||
|
||||
void output_buttons(std::ostream &out) const;
|
||||
void write_buttons(std::ostream &out, int indent_level) const;
|
||||
void write_axes(std::ostream &out, int indent_level) const;
|
||||
|
||||
protected:
|
||||
typedef pdeque<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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file inputDeviceSet.cxx
|
||||
* @author rdb
|
||||
* @date 2015-12-16
|
||||
*/
|
||||
|
||||
#include "inputDeviceSet.h"
|
||||
#include "indent.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
InputDeviceSet::
|
||||
InputDeviceSet() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
InputDeviceSet::
|
||||
InputDeviceSet(const InputDeviceSet ©) :
|
||||
_devices(copy._devices)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void InputDeviceSet::
|
||||
operator = (const InputDeviceSet ©) {
|
||||
_devices = copy._devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new InputDevice to the collection.
|
||||
*/
|
||||
void InputDeviceSet::
|
||||
add_device(InputDevice *device) {
|
||||
_devices.insert(device);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the indicated InputDevice from the collection.
|
||||
* @return true if the device was removed, false if it was not a member.
|
||||
*/
|
||||
bool InputDeviceSet::
|
||||
remove_device(InputDevice *device) {
|
||||
return _devices.erase(device) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the InputDevices indicated in the other collection to this set.
|
||||
*/
|
||||
void InputDeviceSet::
|
||||
add_devices_from(const InputDeviceSet &other) {
|
||||
size_t other_num_devices = other.size();
|
||||
for (size_t i = 0; i < other_num_devices; i++) {
|
||||
_devices.push_back(other[i]);
|
||||
}
|
||||
_devices.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from this collection all of the devices listed in the other set.
|
||||
*/
|
||||
void InputDeviceSet::
|
||||
remove_devices_from(const InputDeviceSet &other) {
|
||||
InputDevices new_devices;
|
||||
InputDevices::const_iterator it;
|
||||
for (it = _devices.begin(); it != _devices.end(); ++it) {
|
||||
if (!other.has_device(*it)) {
|
||||
new_devices.push_back(*it);
|
||||
}
|
||||
}
|
||||
new_devices.sort();
|
||||
_devices.swap(new_devices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the indicated InputDevice appears in this collection, false
|
||||
* otherwise.
|
||||
*/
|
||||
bool InputDeviceSet::
|
||||
has_device(InputDevice *device) const {
|
||||
InputDevices::const_iterator it = _devices.find(device);
|
||||
return (it != _devices.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all InputDevices from the collection.
|
||||
*/
|
||||
void InputDeviceSet::
|
||||
clear() {
|
||||
_devices.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a hint to Panda to allocate enough memory to hold the given number
|
||||
* of InputDevices, if you know ahead of time how many you will be adding.
|
||||
*/
|
||||
void InputDeviceSet::
|
||||
reserve(size_t num) {
|
||||
_devices.reserve(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a brief one-line description of the InputDeviceSet to the indicated
|
||||
* output stream.
|
||||
*/
|
||||
void InputDeviceSet::
|
||||
output(std::ostream &out) const {
|
||||
if (_devices.size() == 1) {
|
||||
out << "1 input device";
|
||||
} else {
|
||||
out << _devices.size() << " input devices";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a complete multi-line description of the InputDeviceSet to the
|
||||
* indicated output stream.
|
||||
*/
|
||||
void InputDeviceSet::
|
||||
write(std::ostream &out, int indent_level) const {
|
||||
output(indent(out, indent_level));
|
||||
out << ":\n";
|
||||
indent_level += 2;
|
||||
|
||||
InputDevices::const_iterator it;
|
||||
for (it = _devices.begin(); it != _devices.end(); ++it) {
|
||||
(*it)->output(indent(out, indent_level));
|
||||
out << '\n';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file inputDeviceSet.h
|
||||
* @author rdb
|
||||
* @date 2015-12-16
|
||||
*/
|
||||
|
||||
#ifndef INPUTDEVICESET_H
|
||||
#define INPUTDEVICESET_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "ordered_vector.h"
|
||||
#include "inputDevice.h"
|
||||
|
||||
/**
|
||||
* Manages a list of InputDevice objects, as returned by various
|
||||
* InputDeviceManager methods. This is implemented like a set, meaning the
|
||||
* same device cannot occur more than once.
|
||||
*/
|
||||
class EXPCL_PANDA_DEVICE InputDeviceSet {
|
||||
PUBLISHED:
|
||||
InputDeviceSet();
|
||||
InputDeviceSet(const InputDeviceSet ©);
|
||||
void operator = (const InputDeviceSet ©);
|
||||
INLINE ~InputDeviceSet();
|
||||
|
||||
public:
|
||||
void add_device(InputDevice *device);
|
||||
bool remove_device(InputDevice *device);
|
||||
void add_devices_from(const InputDeviceSet &other);
|
||||
void remove_devices_from(const InputDeviceSet &other);
|
||||
bool has_device(InputDevice *device) const;
|
||||
|
||||
PUBLISHED:
|
||||
void clear();
|
||||
void reserve(size_t num);
|
||||
|
||||
INLINE InputDevice *operator [] (size_t index) const;
|
||||
INLINE size_t size() const;
|
||||
|
||||
void output(std::ostream &out) const;
|
||||
void write(std::ostream &out, int indent_level = 0) const;
|
||||
|
||||
private:
|
||||
// This is currently implemented as ov_set instead of a regular set so that
|
||||
// we can still support random access.
|
||||
typedef ov_set<PT(InputDevice)> InputDevices;
|
||||
InputDevices _devices;
|
||||
};
|
||||
|
||||
INLINE std::ostream &operator << (std::ostream &out, const InputDeviceSet &col) {
|
||||
col.output(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
#include "inputDeviceSet.I"
|
||||
|
||||
#endif
|
||||
|
|
@ -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__
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,7 @@
|
|||
#include "mouseButton.h"
|
||||
#include "keyboardButton.h"
|
||||
|
||||
#define EXPCL EXPCL_PANDA_DISPLAY
|
||||
#define EXPTP EXPTP_PANDA_DISPLAY
|
||||
#define TYPE GraphicsWindowInputDevice
|
||||
#define NAME vector_GraphicsWindowInputDevice
|
||||
|
||||
#include "vector_src.cxx"
|
||||
TypeHandle GraphicsWindowInputDevice::_type_handle;
|
||||
|
||||
using std::string;
|
||||
|
||||
|
|
@ -34,206 +29,43 @@ using std::string;
|
|||
* below.
|
||||
*/
|
||||
GraphicsWindowInputDevice::
|
||||
GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) :
|
||||
_host(host),
|
||||
_name(name),
|
||||
_flags(flags),
|
||||
_device_index(0),
|
||||
_event_sequence(0),
|
||||
_pointer_mode_enable(false),
|
||||
_pointer_speed(1.0),
|
||||
_enable_pointer_events(false)
|
||||
GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, bool pointer, bool keyboard) :
|
||||
InputDevice(name, DeviceClass::virtual_device)
|
||||
{
|
||||
if (pointer) {
|
||||
enable_feature(Feature::pointer);
|
||||
add_pointer(PointerType::mouse, 0);
|
||||
}
|
||||
if (keyboard) {
|
||||
enable_feature(Feature::keyboard);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This named constructor returns an input device that only has a pointing
|
||||
* device, no keyboard.
|
||||
*/
|
||||
GraphicsWindowInputDevice GraphicsWindowInputDevice::
|
||||
PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice::
|
||||
pointer_only(GraphicsWindow *host, const string &name) {
|
||||
return GraphicsWindowInputDevice(host, name, IDF_has_pointer);
|
||||
return new GraphicsWindowInputDevice(host, name, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This named constructor returns an input device that only has a keyboard, no
|
||||
* pointing device.
|
||||
*/
|
||||
GraphicsWindowInputDevice GraphicsWindowInputDevice::
|
||||
PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice::
|
||||
keyboard_only(GraphicsWindow *host, const string &name) {
|
||||
return GraphicsWindowInputDevice(host, name, IDF_has_keyboard);
|
||||
return new GraphicsWindowInputDevice(host, name, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This named constructor returns an input device that has both a keyboard and
|
||||
* pointer.
|
||||
*/
|
||||
GraphicsWindowInputDevice GraphicsWindowInputDevice::
|
||||
PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice::
|
||||
pointer_and_keyboard(GraphicsWindow *host, const string &name) {
|
||||
return
|
||||
GraphicsWindowInputDevice(host, name, IDF_has_pointer | IDF_has_keyboard);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
GraphicsWindowInputDevice::
|
||||
GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©)
|
||||
{
|
||||
*this = copy;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void GraphicsWindowInputDevice::
|
||||
operator = (const GraphicsWindowInputDevice ©)
|
||||
{
|
||||
LightMutexHolder holder(_lock);
|
||||
LightMutexHolder holder1(copy._lock);
|
||||
_host = copy._host;
|
||||
_name = copy._name;
|
||||
_flags = copy._flags;
|
||||
_device_index = copy._device_index;
|
||||
_event_sequence = copy._event_sequence;
|
||||
_pointer_mode_enable = copy._pointer_mode_enable;
|
||||
_pointer_speed = copy._pointer_speed;
|
||||
_enable_pointer_events = copy._enable_pointer_events;
|
||||
_mouse_data = copy._mouse_data;
|
||||
_true_mouse_data = copy._true_mouse_data;
|
||||
_button_events = copy._button_events;
|
||||
_pointer_events = copy._pointer_events;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
GraphicsWindowInputDevice::
|
||||
~GraphicsWindowInputDevice() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this device has a pending button event (a mouse button or
|
||||
* keyboard button down/up), false otherwise. If this returns true, the
|
||||
* particular event may be extracted via get_button_event().
|
||||
*/
|
||||
bool GraphicsWindowInputDevice::
|
||||
has_button_event() const {
|
||||
LightMutexHolder holder(_lock);
|
||||
return !_button_events.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assuming a previous call to has_button_event() returned true, this returns
|
||||
* the pending button event.
|
||||
*/
|
||||
ButtonEvent GraphicsWindowInputDevice::
|
||||
get_button_event() {
|
||||
LightMutexHolder holder(_lock);
|
||||
ButtonEvent be = _button_events.front();
|
||||
_button_events.pop_front();
|
||||
return be;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this device has a pending pointer event (a mouse movement),
|
||||
* or false otherwise. If this returns true, the particular event may be
|
||||
* extracted via get_pointer_event().
|
||||
*/
|
||||
bool GraphicsWindowInputDevice::
|
||||
has_pointer_event() const {
|
||||
LightMutexHolder holder(_lock);
|
||||
return (_pointer_events != nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PointerEventList containing all the recent pointer events.
|
||||
*/
|
||||
PT(PointerEventList) GraphicsWindowInputDevice::
|
||||
get_pointer_events() {
|
||||
LightMutexHolder holder(_lock);
|
||||
PT(PointerEventList) result = _pointer_events;
|
||||
_pointer_events = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* There are two modes: raw mode, and pointer mode. In pointer mode, the
|
||||
* mouse stops when it reaches the edges of the window. In raw mode, the
|
||||
* mouse ignores the screen boundaries and can continue indefinitely, even
|
||||
* into negative coordinates. In raw mode, each "blip" from the mouse
|
||||
* hardware corresponds to a change of 1 unit in the mouse's (x,y) coordinate.
|
||||
* In pointer mode, a variety of speed adjustment factors and concepts like
|
||||
* "mouse acceleration" may be applied.
|
||||
*
|
||||
* Mouse zero represents the system mouse pointer. This is by definition a
|
||||
* pointer, not a raw mouse. It is an error to try to enable or disable
|
||||
* pointer mode on mouse zero.
|
||||
*/
|
||||
void GraphicsWindowInputDevice::
|
||||
enable_pointer_mode(double speed) {
|
||||
LightMutexHolder holder(_lock);
|
||||
nassertv(_device_index != 0);
|
||||
_pointer_mode_enable = true;
|
||||
_pointer_speed = speed;
|
||||
_mouse_data._xpos = _host->get_x_size() * 0.5;
|
||||
_mouse_data._ypos = _host->get_y_size() * 0.5;
|
||||
_mouse_data._in_window = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* see enable_pointer_mode.
|
||||
*/
|
||||
void GraphicsWindowInputDevice::
|
||||
disable_pointer_mode() {
|
||||
LightMutexHolder holder(_lock);
|
||||
nassertv(_device_index != 0);
|
||||
_pointer_mode_enable = false;
|
||||
_pointer_speed = 1.0;
|
||||
_mouse_data = _true_mouse_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records that a mouse movement has taken place.
|
||||
*/
|
||||
void GraphicsWindowInputDevice::
|
||||
set_pointer(bool inwin, double x, double y, double time) {
|
||||
LightMutexHolder holder(_lock);
|
||||
|
||||
double delta_x = x - _true_mouse_data._xpos;
|
||||
double delta_y = y - _true_mouse_data._ypos;
|
||||
_true_mouse_data._in_window = inwin;
|
||||
_true_mouse_data._xpos = x;
|
||||
_true_mouse_data._ypos = y;
|
||||
|
||||
if (_pointer_mode_enable) {
|
||||
double pointer_x = _mouse_data._xpos;
|
||||
double pointer_y = _mouse_data._ypos;
|
||||
pointer_x += (delta_x * _pointer_speed);
|
||||
pointer_y += (delta_y * _pointer_speed);
|
||||
double xhi = _host->get_x_size();
|
||||
double yhi = _host->get_y_size();
|
||||
if (pointer_x < 0.0) pointer_x = 0.0;
|
||||
if (pointer_y < 0.0) pointer_y = 0.0;
|
||||
if (pointer_x > xhi) pointer_x = xhi;
|
||||
if (pointer_y > yhi) pointer_y = yhi;
|
||||
_mouse_data._in_window = true;
|
||||
_mouse_data._xpos = pointer_x;
|
||||
_mouse_data._ypos = pointer_y;
|
||||
} else {
|
||||
_mouse_data = _true_mouse_data;
|
||||
}
|
||||
|
||||
if (_enable_pointer_events) {
|
||||
int seq = _event_sequence++;
|
||||
if (_pointer_events == nullptr) {
|
||||
_pointer_events = new PointerEventList();
|
||||
}
|
||||
_pointer_events->add_event(_mouse_data._in_window,
|
||||
_mouse_data._xpos,
|
||||
_mouse_data._ypos,
|
||||
seq, time);
|
||||
}
|
||||
return new GraphicsWindowInputDevice(host, name, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -242,7 +74,7 @@ set_pointer(bool inwin, double x, double y, double time) {
|
|||
void GraphicsWindowInputDevice::
|
||||
button_down(ButtonHandle button, double time) {
|
||||
LightMutexHolder holder(_lock);
|
||||
_button_events.push_back(ButtonEvent(button, ButtonEvent::T_down, time));
|
||||
_button_events->add_event(ButtonEvent(button, ButtonEvent::T_down, time));
|
||||
_buttons_held.insert(button);
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +86,7 @@ button_down(ButtonHandle button, double time) {
|
|||
void GraphicsWindowInputDevice::
|
||||
button_resume_down(ButtonHandle button, double time) {
|
||||
LightMutexHolder holder(_lock);
|
||||
_button_events.push_back(ButtonEvent(button, ButtonEvent::T_resume_down, time));
|
||||
_button_events->add_event(ButtonEvent(button, ButtonEvent::T_resume_down, time));
|
||||
_buttons_held.insert(button);
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +96,7 @@ button_resume_down(ButtonHandle button, double time) {
|
|||
void GraphicsWindowInputDevice::
|
||||
button_up(ButtonHandle button, double time) {
|
||||
LightMutexHolder holder(_lock);
|
||||
_button_events.push_back(ButtonEvent(button, ButtonEvent::T_up, time));
|
||||
_button_events->add_event(ButtonEvent(button, ButtonEvent::T_up, time));
|
||||
_buttons_held.erase(button);
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +106,7 @@ button_up(ButtonHandle button, double time) {
|
|||
void GraphicsWindowInputDevice::
|
||||
keystroke(int keycode, double time) {
|
||||
LightMutexHolder holder(_lock);
|
||||
_button_events.push_back(ButtonEvent(keycode, time));
|
||||
_button_events->add_event(ButtonEvent(keycode, time));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -286,7 +118,7 @@ void GraphicsWindowInputDevice::
|
|||
candidate(const std::wstring &candidate_string, size_t highlight_start,
|
||||
size_t highlight_end, size_t cursor_pos) {
|
||||
LightMutexHolder holder(_lock);
|
||||
_button_events.push_back(ButtonEvent(candidate_string,
|
||||
_button_events->add_event(ButtonEvent(candidate_string,
|
||||
highlight_start, highlight_end,
|
||||
cursor_pos));
|
||||
}
|
||||
|
|
@ -303,7 +135,7 @@ focus_lost(double time) {
|
|||
LightMutexHolder holder(_lock);
|
||||
ButtonsHeld::iterator bi;
|
||||
for (bi = _buttons_held.begin(); bi != _buttons_held.end(); ++bi) {
|
||||
_button_events.push_back(ButtonEvent(*bi, ButtonEvent::T_up, time));
|
||||
_button_events->add_event(ButtonEvent(*bi, ButtonEvent::T_up, time));
|
||||
}
|
||||
_buttons_held.clear();
|
||||
}
|
||||
|
|
@ -314,7 +146,7 @@ focus_lost(double time) {
|
|||
void GraphicsWindowInputDevice::
|
||||
raw_button_down(ButtonHandle button, double time) {
|
||||
LightMutexHolder holder(_lock);
|
||||
_button_events.push_back(ButtonEvent(button, ButtonEvent::T_raw_down, time));
|
||||
_button_events->add_event(ButtonEvent(button, ButtonEvent::T_raw_down, time));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -323,5 +155,47 @@ raw_button_down(ButtonHandle button, double time) {
|
|||
void GraphicsWindowInputDevice::
|
||||
raw_button_up(ButtonHandle button, double time) {
|
||||
LightMutexHolder holder(_lock);
|
||||
_button_events.push_back(ButtonEvent(button, ButtonEvent::T_raw_up, time));
|
||||
_button_events->add_event(ButtonEvent(button, ButtonEvent::T_raw_up, time));
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by a particular kind of GraphicsWindow to indicate that the
|
||||
* pointer is within the window, at the given pixel coordinates.
|
||||
*/
|
||||
void GraphicsWindowInputDevice::
|
||||
set_pointer_in_window(double x, double y, double time) {
|
||||
LightMutexHolder holder(_lock);
|
||||
PointerData data = _pointers[0];
|
||||
data._id = 0;
|
||||
data._type = PointerType::mouse;
|
||||
data._xpos = x;
|
||||
data._ypos = y;
|
||||
data._in_window = true;
|
||||
InputDevice::update_pointer(data, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by a particular kind of GraphicsWindow to indicate that the
|
||||
* pointer is no longer within the window.
|
||||
*/
|
||||
void GraphicsWindowInputDevice::
|
||||
set_pointer_out_of_window(double time) {
|
||||
LightMutexHolder holder(_lock);
|
||||
if (_pointers.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_pointers[0]._in_window = false;
|
||||
_pointers[0]._pressure = 0.0;
|
||||
|
||||
if (_enable_pointer_events) {
|
||||
int seq = _event_sequence++;
|
||||
if (_pointer_events.is_null()) {
|
||||
_pointer_events = new PointerEventList();
|
||||
}
|
||||
_pointer_events->add_event(_pointers[0]._in_window,
|
||||
_pointers[0]._xpos,
|
||||
_pointers[0]._ypos,
|
||||
seq, time);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,131 +15,73 @@
|
|||
#define GRAPHICSWINDOWINPUTDEVICE_H
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
#include "buttonEvent.h"
|
||||
#include "pointerEvent.h"
|
||||
#include "pointerEventList.h"
|
||||
#include "mouseData.h"
|
||||
#include "clockObject.h"
|
||||
|
||||
#include "pdeque.h"
|
||||
#include "pvector.h"
|
||||
#include "lightMutex.h"
|
||||
#include "lightMutexHolder.h"
|
||||
#include "inputDevice.h"
|
||||
|
||||
// Forward declarations
|
||||
class GraphicsWindow;
|
||||
|
||||
/**
|
||||
* This is a structure representing a single input device that may be
|
||||
* associated with a window. Typically this will be a keyboard/mouse pair,
|
||||
* and there will be exactly one of these associated with each window, but
|
||||
* other variants are possible.
|
||||
* This is a virtual input device that represents the keyboard and mouse pair
|
||||
* that is associated with a particular window. It collects mouse and
|
||||
* keyboard events from the windowing system while the window is in focus.
|
||||
*/
|
||||
class EXPCL_PANDA_DISPLAY GraphicsWindowInputDevice {
|
||||
class EXPCL_PANDA_DISPLAY GraphicsWindowInputDevice : public InputDevice {
|
||||
private:
|
||||
GraphicsWindowInputDevice(GraphicsWindow *host, const std::string &name, int flags);
|
||||
GraphicsWindowInputDevice(GraphicsWindow *host, const std::string &name,
|
||||
bool pointer, bool keyboard);
|
||||
|
||||
public:
|
||||
static GraphicsWindowInputDevice pointer_only(GraphicsWindow *host, const std::string &name);
|
||||
static GraphicsWindowInputDevice keyboard_only(GraphicsWindow *host, const std::string &name);
|
||||
static GraphicsWindowInputDevice pointer_and_keyboard(GraphicsWindow *host, const std::string &name);
|
||||
static PT(GraphicsWindowInputDevice) pointer_only(GraphicsWindow *host, const std::string &name);
|
||||
static PT(GraphicsWindowInputDevice) keyboard_only(GraphicsWindow *host, const std::string &name);
|
||||
static PT(GraphicsWindowInputDevice) pointer_and_keyboard(GraphicsWindow *host, const std::string &name);
|
||||
|
||||
INLINE GraphicsWindowInputDevice();
|
||||
GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©);
|
||||
void operator = (const GraphicsWindowInputDevice ©);
|
||||
~GraphicsWindowInputDevice();
|
||||
|
||||
INLINE std::string get_name() const;
|
||||
INLINE bool has_pointer() const;
|
||||
INLINE bool has_keyboard() const;
|
||||
|
||||
INLINE void set_device_index(int index);
|
||||
|
||||
INLINE MouseData get_pointer() const;
|
||||
INLINE MouseData get_raw_pointer() const;
|
||||
|
||||
INLINE void enable_pointer_events();
|
||||
INLINE void disable_pointer_events();
|
||||
|
||||
void enable_pointer_mode(double speed);
|
||||
void disable_pointer_mode();
|
||||
|
||||
bool has_button_event() const;
|
||||
ButtonEvent get_button_event();
|
||||
bool has_pointer_event() const;
|
||||
PT(PointerEventList) get_pointer_events();
|
||||
GraphicsWindowInputDevice() = default;
|
||||
|
||||
PUBLISHED:
|
||||
// The following interface is for the various kinds of GraphicsWindows to
|
||||
// record the data incoming on the device.
|
||||
INLINE void button_down(ButtonHandle button);
|
||||
INLINE void button_resume_down(ButtonHandle button);
|
||||
INLINE void button_up(ButtonHandle button);
|
||||
INLINE void keystroke(int keycode);
|
||||
INLINE void focus_lost();
|
||||
INLINE void raw_button_down(ButtonHandle button);
|
||||
INLINE void raw_button_up(ButtonHandle button);
|
||||
INLINE void set_pointer_in_window(double x, double y);
|
||||
INLINE void set_pointer_out_of_window();
|
||||
void button_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time());
|
||||
void button_resume_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time());
|
||||
void button_up(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time());
|
||||
|
||||
void button_down(ButtonHandle button, double time);
|
||||
void button_resume_down(ButtonHandle button, double time);
|
||||
void button_up(ButtonHandle button, double time);
|
||||
void keystroke(int keycode, double time);
|
||||
void keystroke(int keycode, double time = ClockObject::get_global_clock()->get_frame_time());
|
||||
void candidate(const std::wstring &candidate_string, size_t highlight_start,
|
||||
size_t highlight_end, size_t cursor_pos);
|
||||
void focus_lost(double time);
|
||||
void raw_button_down(ButtonHandle button, double time);
|
||||
void raw_button_up(ButtonHandle button, double time);
|
||||
|
||||
INLINE void set_pointer_in_window(double x, double y, double time);
|
||||
INLINE void set_pointer_out_of_window(double time);
|
||||
void set_pointer(bool inwin, double x, double y, double time);
|
||||
void focus_lost(double time = ClockObject::get_global_clock()->get_frame_time());
|
||||
|
||||
public:
|
||||
// We need these methods to make VC++ happy when we try to instantiate a
|
||||
// pvector<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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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() {
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ private:
|
|||
private:
|
||||
PT(GraphicsBuffer) _buffer;
|
||||
PT(Texture) _texture;
|
||||
PT(GraphicsWindowInputDevice) _input;
|
||||
|
||||
int _fd;
|
||||
size_t _mmap_size;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class DatagramIterator;
|
|||
* but it may be used anywhere a list of ButtonEvents is desired.
|
||||
*/
|
||||
class EXPCL_PANDA_EVENT ButtonEventList : public ParamValueBase {
|
||||
public:
|
||||
PUBLISHED:
|
||||
INLINE ButtonEventList();
|
||||
INLINE ButtonEventList(const ButtonEventList ©);
|
||||
INLINE void operator = (const ButtonEventList ©);
|
||||
|
|
|
|||
|
|
@ -11,59 +11,6 @@
|
|||
* @date 2007-09-20
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE PointerEvent::
|
||||
PointerEvent() :
|
||||
_in_window(false),
|
||||
_xpos(0),
|
||||
_ypos(0),
|
||||
_dx(0),
|
||||
_dy(0),
|
||||
_length(0.0),
|
||||
_direction(0.0),
|
||||
_rotation(0.0),
|
||||
_sequence(0),
|
||||
_time(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE PointerEvent::
|
||||
PointerEvent(const PointerEvent ©) :
|
||||
_in_window(copy._in_window),
|
||||
_xpos(copy._xpos),
|
||||
_ypos(copy._ypos),
|
||||
_dx(copy._dx),
|
||||
_dy(copy._dy),
|
||||
_length(copy._length),
|
||||
_direction(copy._direction),
|
||||
_rotation(copy._rotation),
|
||||
_sequence(copy._sequence),
|
||||
_time(copy._time)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void PointerEvent::
|
||||
operator = (const PointerEvent ©) {
|
||||
_in_window = copy._in_window;
|
||||
_xpos = copy._xpos;
|
||||
_ypos = copy._ypos;
|
||||
_dx = copy._dx;
|
||||
_dy = copy._dy;
|
||||
_sequence = copy._sequence;
|
||||
_length = copy._length;
|
||||
_direction = copy._direction;
|
||||
_rotation = copy._rotation;
|
||||
_time = copy._time;
|
||||
}
|
||||
|
||||
/**
|
||||
* The equality operator does not consider time significant.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
#define POINTEREVENT_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "mouseData.h"
|
||||
#include "pointerData.h"
|
||||
|
||||
class Datagram;
|
||||
class DatagramIterator;
|
||||
|
|
@ -25,10 +25,7 @@ class DatagramIterator;
|
|||
*/
|
||||
class EXPCL_PANDA_EVENT PointerEvent {
|
||||
public:
|
||||
|
||||
INLINE PointerEvent();
|
||||
INLINE PointerEvent(const PointerEvent ©);
|
||||
INLINE void operator = (const PointerEvent ©);
|
||||
PointerEvent() = default;
|
||||
|
||||
INLINE bool operator == (const PointerEvent &other) const;
|
||||
INLINE bool operator != (const PointerEvent &other) const;
|
||||
|
|
@ -40,16 +37,19 @@ public:
|
|||
void read_datagram(DatagramIterator &scan);
|
||||
|
||||
public:
|
||||
bool _in_window;
|
||||
int _xpos;
|
||||
int _ypos;
|
||||
int _dx;
|
||||
int _dy;
|
||||
double _length;
|
||||
double _direction;
|
||||
double _rotation;
|
||||
int _sequence;
|
||||
double _time;
|
||||
bool _in_window = false;
|
||||
int _id = 0;
|
||||
PointerType _type = PointerType::unknown;
|
||||
double _xpos = 0.0;
|
||||
double _ypos = 0.0;
|
||||
double _dx = 0.0;
|
||||
double _dy = 0.0;
|
||||
double _length = 0.0;
|
||||
double _direction = 0.0;
|
||||
double _pressure = 0.0;
|
||||
double _rotation = 0.0;
|
||||
int _sequence = 0;
|
||||
double _time = 0.0;
|
||||
};
|
||||
|
||||
INLINE std::ostream &operator << (std::ostream &out, const PointerEvent &pe) {
|
||||
|
|
|
|||
|
|
@ -38,99 +38,115 @@ operator = (const PointerEventList ©) {
|
|||
/**
|
||||
* Returns the number of events in the list.
|
||||
*/
|
||||
INLINE int PointerEventList::
|
||||
INLINE size_t PointerEventList::
|
||||
get_num_events() const {
|
||||
return _events.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the list is empty.
|
||||
*/
|
||||
INLINE bool PointerEventList::
|
||||
empty() const {
|
||||
return _events.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nth event in the list.
|
||||
*/
|
||||
INLINE const PointerEvent &PointerEventList::
|
||||
get_event(size_t n) const {
|
||||
return _events[n];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the in-window flag of the nth event.
|
||||
*/
|
||||
INLINE bool PointerEventList::
|
||||
get_in_window(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._in_window;
|
||||
get_in_window(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._in_window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x-coordinate of the nth event.
|
||||
*/
|
||||
INLINE int PointerEventList::
|
||||
get_xpos(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._xpos;
|
||||
get_xpos(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._xpos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y-coordinate of the nth event.
|
||||
*/
|
||||
INLINE int PointerEventList::
|
||||
get_ypos(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._ypos;
|
||||
get_ypos(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._ypos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x-coordinate of the nth event.
|
||||
* Get the x-delta of the nth event.
|
||||
*/
|
||||
INLINE int PointerEventList::
|
||||
get_dx(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._dx;
|
||||
INLINE double PointerEventList::
|
||||
get_dx(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._dx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y-coordinate of the nth event.
|
||||
* Get the y-delta of the nth event.
|
||||
*/
|
||||
INLINE int PointerEventList::
|
||||
get_dy(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._dy;
|
||||
INLINE double PointerEventList::
|
||||
get_dy(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the nth event.
|
||||
*/
|
||||
INLINE double PointerEventList::
|
||||
get_length(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._length;
|
||||
get_length(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the direction of the nth event.
|
||||
*/
|
||||
INLINE double PointerEventList::
|
||||
get_direction(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._direction;
|
||||
get_direction(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rotation of the nth event.
|
||||
*/
|
||||
INLINE double PointerEventList::
|
||||
get_rotation(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._rotation;
|
||||
get_rotation(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sequence number of the nth event.
|
||||
*/
|
||||
INLINE int PointerEventList::
|
||||
get_sequence(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._sequence;
|
||||
get_sequence(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of the nth event.
|
||||
*/
|
||||
INLINE double PointerEventList::
|
||||
get_time(int evt) const {
|
||||
nassertr((evt >= 0) && (evt < (int)_events.size()), 0);
|
||||
return _events[evt]._time;
|
||||
get_time(size_t n) const {
|
||||
nassertr(n < _events.size(), 0);
|
||||
return _events[n]._time;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -34,21 +34,24 @@ class EXPCL_PANDA_EVENT PointerEventList : public ParamValueBase {
|
|||
PUBLISHED:
|
||||
INLINE PointerEventList();
|
||||
|
||||
INLINE int get_num_events() const;
|
||||
INLINE bool get_in_window(int n) const;
|
||||
INLINE int get_xpos(int n) const;
|
||||
INLINE int get_ypos(int n) const;
|
||||
INLINE int get_dx(int n) const;
|
||||
INLINE int get_dy(int n) const;
|
||||
INLINE int get_sequence(int n) const;
|
||||
INLINE double get_length(int n) const;
|
||||
INLINE double get_direction(int n) const;
|
||||
INLINE double get_rotation(int n) const;
|
||||
INLINE double get_time(int n) const;
|
||||
INLINE size_t get_num_events() const;
|
||||
INLINE bool get_in_window(size_t n) const;
|
||||
INLINE int get_xpos(size_t n) const;
|
||||
INLINE int get_ypos(size_t n) const;
|
||||
INLINE double get_dx(size_t n) const;
|
||||
INLINE double get_dy(size_t n) const;
|
||||
INLINE int get_sequence(size_t n) const;
|
||||
INLINE double get_length(size_t n) const;
|
||||
INLINE double get_direction(size_t n) const;
|
||||
INLINE double get_rotation(size_t n) const;
|
||||
INLINE double get_time(size_t n) const;
|
||||
|
||||
INLINE void clear();
|
||||
INLINE void pop_front();
|
||||
void add_event(bool in_win, int xpos, int ypos, int seq, double time);
|
||||
void add_event(const PointerData &data, int seq, double time);
|
||||
void add_event(bool in_win, int xpos, int ypos, int seq, double time);
|
||||
void add_event(bool in_win, int xpos, int ypos, double xdelta, double ydelta,
|
||||
int seq, double time);
|
||||
|
||||
bool encircles(int x, int y) const;
|
||||
double total_turns(double sec) const;
|
||||
|
|
@ -58,6 +61,9 @@ public:
|
|||
INLINE PointerEventList(const PointerEventList ©);
|
||||
INLINE void operator = (const PointerEventList ©);
|
||||
|
||||
INLINE bool empty() const;
|
||||
INLINE const PointerEvent &get_event(size_t n) const;
|
||||
|
||||
virtual void output(std::ostream &out) const;
|
||||
void write(std::ostream &out, int indent_level = 0) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
/**
|
||||
* PANDA 3D SOFTWARE
|
||||
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
||||
*
|
||||
* All use of this software is subject to the terms of the revised BSD
|
||||
* license. You should have received a copy of this license along
|
||||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file mouseData.I
|
||||
* @author drose
|
||||
* @date 2002-07-15
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE MouseData::
|
||||
MouseData() {
|
||||
_in_window = false;
|
||||
_xpos = 0;
|
||||
_ypos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE MouseData::
|
||||
MouseData(const MouseData ©) :
|
||||
_in_window(copy._in_window),
|
||||
_xpos(copy._xpos),
|
||||
_ypos(copy._ypos)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void MouseData::
|
||||
operator = (const MouseData ©) {
|
||||
_in_window = copy._in_window;
|
||||
_xpos = copy._xpos;
|
||||
_ypos = copy._ypos;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE double MouseData::
|
||||
get_x() const {
|
||||
return _xpos;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE double MouseData::
|
||||
get_y() const {
|
||||
return _ypos;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE bool MouseData::
|
||||
get_in_window() const {
|
||||
return _in_window;
|
||||
}
|
||||
|
||||
|
||||
INLINE std::ostream &operator << (std::ostream &out, const MouseData &md) {
|
||||
md.output(out);
|
||||
return out;
|
||||
}
|
||||
|
|
@ -7,45 +7,21 @@
|
|||
* with this source code in a file named "LICENSE."
|
||||
*
|
||||
* @file mouseData.h
|
||||
* @author drose
|
||||
* @date 1999-02-08
|
||||
* @author rdb
|
||||
* @date 2018-09-24
|
||||
*/
|
||||
|
||||
#ifndef MOUSEDATA_H
|
||||
#define MOUSEDATA_H
|
||||
|
||||
#include "pandabase.h"
|
||||
|
||||
#include "modifierButtons.h"
|
||||
#include "pointerData.h"
|
||||
|
||||
BEGIN_PUBLISH
|
||||
/**
|
||||
* Holds the data that might be generated by a 2-d pointer input device, such
|
||||
* as the mouse in the GraphicsWindow.
|
||||
* Deprecated alias for PointerData.
|
||||
*/
|
||||
class EXPCL_PANDA_PUTIL MouseData {
|
||||
PUBLISHED:
|
||||
INLINE MouseData();
|
||||
INLINE MouseData(const MouseData ©);
|
||||
INLINE void operator = (const MouseData ©);
|
||||
typedef PointerData MouseData;
|
||||
|
||||
INLINE double get_x() const;
|
||||
INLINE double get_y() const;
|
||||
INLINE bool get_in_window() const;
|
||||
|
||||
void output(std::ostream &out) const;
|
||||
|
||||
MAKE_PROPERTY(x, get_x);
|
||||
MAKE_PROPERTY(y, get_y);
|
||||
MAKE_PROPERTY(in_window, get_in_window);
|
||||
|
||||
public:
|
||||
bool _in_window;
|
||||
double _xpos;
|
||||
double _ypos;
|
||||
};
|
||||
|
||||
INLINE std::ostream &operator << (std::ostream &out, const MouseData &md);
|
||||
|
||||
#include "mouseData.I"
|
||||
END_PUBLISH
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -29,4 +29,3 @@
|
|||
#include "factoryBase.cxx"
|
||||
#include "factoryParam.cxx"
|
||||
#include "factoryParams.cxx"
|
||||
#include "globalPointerRegistry.cxx"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#include "gamepadButton.cxx"
|
||||
#include "globalPointerRegistry.cxx"
|
||||
#include "ioPtaDatagramFloat.cxx"
|
||||
#include "ioPtaDatagramInt.cxx"
|
||||
#include "ioPtaDatagramShort.cxx"
|
||||
|
|
@ -7,11 +9,11 @@
|
|||
#include "loaderOptions.cxx"
|
||||
#include "modifierButtons.cxx"
|
||||
#include "mouseButton.cxx"
|
||||
#include "mouseData.cxx"
|
||||
#include "nameUniquifier.cxx"
|
||||
#include "nodeCachedReferenceCount.cxx"
|
||||
#include "paramValue.cxx"
|
||||
#include "pbitops.cxx"
|
||||
#include "pointerData.cxx"
|
||||
#include "pta_ushort.cxx"
|
||||
#include "simpleHashMap.cxx"
|
||||
#include "sparseArray.cxx"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 << ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue