From bd0683d1017dea4cbd41a545f55008581d3a3630 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 11 Dec 2015 15:26:40 +0100 Subject: [PATCH 001/424] Generalised input framework for raw mice, gamepads, trackers (incl. HMDs like Oculus Rift) --- makepanda/makepanda.py | 30 +- panda/src/device/analogNode.I | 15 +- panda/src/device/analogNode.cxx | 25 +- panda/src/device/analogNode.h | 3 +- panda/src/device/buttonNode.I | 26 +- panda/src/device/buttonNode.cxx | 42 +- panda/src/device/buttonNode.h | 6 +- panda/src/device/clientAnalogDevice.I | 74 --- panda/src/device/clientAnalogDevice.cxx | 47 +- panda/src/device/clientAnalogDevice.h | 23 - panda/src/device/clientBase.cxx | 1 - panda/src/device/clientButtonDevice.I | 110 ---- panda/src/device/clientButtonDevice.cxx | 113 +--- panda/src/device/clientButtonDevice.h | 38 -- panda/src/device/clientDevice.I | 53 -- panda/src/device/clientDevice.cxx | 25 +- panda/src/device/clientDevice.h | 27 +- panda/src/device/clientTrackerDevice.I | 14 +- panda/src/device/clientTrackerDevice.h | 6 - panda/src/device/config_device.cxx | 11 +- panda/src/device/dialNode.I | 15 +- panda/src/device/evdevInputDevice.I | 14 + panda/src/device/evdevInputDevice.cxx | 424 ++++++++++++++ panda/src/device/evdevInputDevice.h | 63 +++ panda/src/device/inputDevice.I | 445 +++++++++++++++ panda/src/device/inputDevice.cxx | 517 ++++++++++++++++++ panda/src/device/inputDevice.h | 279 ++++++++++ panda/src/device/inputDeviceManager.I | 27 + panda/src/device/inputDeviceManager.cxx | 240 ++++++++ panda/src/device/inputDeviceManager.h | 64 +++ panda/src/device/linuxJoystickDevice.I | 14 + panda/src/device/linuxJoystickDevice.cxx | 313 +++++++++++ panda/src/device/linuxJoystickDevice.h | 60 ++ panda/src/device/p3device_composite1.cxx | 5 +- panda/src/device/p3device_composite2.cxx | 1 - panda/src/device/trackerNode.I | 3 +- panda/src/device/trackerNode.cxx | 19 +- panda/src/device/trackerNode.h | 16 +- panda/src/display/callbackGraphicsWindow.cxx | 20 +- panda/src/display/callbackGraphicsWindow.h | 1 - panda/src/display/config_display.cxx | 6 +- panda/src/display/graphicsWindow.cxx | 117 +--- panda/src/display/graphicsWindow.h | 18 +- panda/src/display/graphicsWindowInputDevice.I | 232 +------- .../src/display/graphicsWindowInputDevice.cxx | 217 +------- panda/src/display/graphicsWindowInputDevice.h | 134 ++--- .../{device => display}/mouseAndKeyboard.cxx | 29 +- .../{device => display}/mouseAndKeyboard.h | 3 +- panda/src/display/p3display_composite2.cxx | 1 + panda/src/putil/config_util.cxx | 2 + panda/src/putil/gamepadButton.cxx | 75 +++ panda/src/putil/gamepadButton.h | 57 ++ panda/src/putil/p3putil_composite1.cxx | 1 - panda/src/putil/p3putil_composite2.cxx | 2 + .../src/tinydisplay/tinyOsxGraphicsWindow.mm | 32 +- .../src/tinydisplay/tinySDLGraphicsWindow.cxx | 20 +- panda/src/tinydisplay/tinyXGraphicsWindow.cxx | 20 +- panda/src/vrpn/vrpnAnalog.cxx | 2 - panda/src/vrpn/vrpnButton.cxx | 2 - panda/src/vrpn/vrpnDial.cxx | 2 - panda/src/vrpn/vrpnTracker.cxx | 31 +- panda/src/windisplay/winGraphicsWindow.cxx | 6 +- panda/src/x11display/x11GraphicsWindow.cxx | 347 +++--------- panda/src/x11display/x11GraphicsWindow.h | 2 +- 64 files changed, 2969 insertions(+), 1618 deletions(-) create mode 100644 panda/src/device/evdevInputDevice.I create mode 100644 panda/src/device/evdevInputDevice.cxx create mode 100644 panda/src/device/evdevInputDevice.h create mode 100644 panda/src/device/inputDevice.I create mode 100644 panda/src/device/inputDevice.cxx create mode 100644 panda/src/device/inputDevice.h create mode 100644 panda/src/device/inputDeviceManager.I create mode 100644 panda/src/device/inputDeviceManager.cxx create mode 100644 panda/src/device/inputDeviceManager.h create mode 100644 panda/src/device/linuxJoystickDevice.I create mode 100644 panda/src/device/linuxJoystickDevice.cxx create mode 100644 panda/src/device/linuxJoystickDevice.h rename panda/src/{device => display}/mouseAndKeyboard.cxx (88%) rename panda/src/{device => display}/mouseAndKeyboard.h (98%) create mode 100644 panda/src/putil/gamepadButton.cxx create mode 100644 panda/src/putil/gamepadButton.h diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 8fe92440bc..d11d0825bd 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -3641,6 +3641,21 @@ if (not RUNTIME): TargetAdd('libp3dgraph.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dgraph', 'SRCDIR:panda/src/dgraph']) TargetAdd('libp3dgraph_igate.obj', input='libp3dgraph.in', opts=["DEPENDENCYONLY"]) +# +# DIRECTORY: panda/src/device/ +# + +if (not RUNTIME): + OPTS=['DIR:panda/src/device', 'BUILDING:PANDA'] + TargetAdd('p3device_composite1.obj', opts=OPTS, input='p3device_composite1.cxx') + TargetAdd('p3device_composite2.obj', opts=OPTS, input='p3device_composite2.cxx') + + OPTS=['DIR:panda/src/device'] + IGATEFILES=GetDirectoryContents('panda/src/device', ["*.h", "*_composite*.cxx"]) + TargetAdd('libp3device.in', opts=OPTS, input=IGATEFILES) + TargetAdd('libp3device.in', opts=['IMOD:panda3d.core', 'ILIB:libp3device', 'SRCDIR:panda/src/device']) + TargetAdd('libp3device_igate.obj', input='libp3device.in', opts=["DEPENDENCYONLY"]) + # # DIRECTORY: panda/src/display/ # @@ -3665,21 +3680,6 @@ if (not RUNTIME): TargetAdd('subprocessWindowBuffer.obj', opts=OPTS, input='subprocessWindowBuffer.cxx') TargetAdd('libp3subprocbuffer.ilb', input='subprocessWindowBuffer.obj') -# -# DIRECTORY: panda/src/device/ -# - -if (not RUNTIME): - OPTS=['DIR:panda/src/device', 'BUILDING:PANDA'] - TargetAdd('p3device_composite1.obj', opts=OPTS, input='p3device_composite1.cxx') - TargetAdd('p3device_composite2.obj', opts=OPTS, input='p3device_composite2.cxx') - - OPTS=['DIR:panda/src/device'] - IGATEFILES=GetDirectoryContents('panda/src/device', ["*.h", "*_composite*.cxx"]) - TargetAdd('libp3device.in', opts=OPTS, input=IGATEFILES) - TargetAdd('libp3device.in', opts=['IMOD:panda3d.core', 'ILIB:libp3device', 'SRCDIR:panda/src/device']) - TargetAdd('libp3device_igate.obj', input='libp3device.in', opts=["DEPENDENCYONLY"]) - # # DIRECTORY: panda/src/pnmtext/ # diff --git a/panda/src/device/analogNode.I b/panda/src/device/analogNode.I index 9747587b73..ebc382fce3 100644 --- a/panda/src/device/analogNode.I +++ b/panda/src/device/analogNode.I @@ -44,10 +44,7 @@ is_valid() const { //////////////////////////////////////////////////////////////////// INLINE int AnalogNode:: get_num_controls() const { - _analog->acquire(); - int result = _analog->get_num_controls(); - _analog->unlock(); - return result; + return _analog->get_num_controls(); } //////////////////////////////////////////////////////////////////// @@ -60,10 +57,7 @@ get_num_controls() const { //////////////////////////////////////////////////////////////////// INLINE double AnalogNode:: get_control_state(int index) const { - _analog->acquire(); - double result = _analog->get_control_state(index); - _analog->unlock(); - return result; + return _analog->get_control_state(index); } //////////////////////////////////////////////////////////////////// @@ -75,10 +69,7 @@ get_control_state(int index) const { //////////////////////////////////////////////////////////////////// INLINE bool AnalogNode:: is_control_known(int index) const { - _analog->acquire(); - bool result = _analog->is_control_known(index); - _analog->unlock(); - return result; + return _analog->is_control_known(index); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/device/analogNode.cxx b/panda/src/device/analogNode.cxx index 1bea47ceb2..6d2cef3f67 100644 --- a/panda/src/device/analogNode.cxx +++ b/panda/src/device/analogNode.cxx @@ -30,7 +30,7 @@ AnalogNode(ClientBase *client, const string &device_name) : DataNode(device_name) { _xy_output = define_output("xy", EventStoreVec2::get_class_type()); - _xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); + _xy = new EventStoreVec2(LPoint2(0)); nassertv(client != (ClientBase *)NULL); PT(ClientDevice) device = @@ -49,7 +49,23 @@ AnalogNode(ClientBase *client, const string &device_name) : return; } - _analog = DCAST(ClientAnalogDevice, device); + _analog = device; +} + +//////////////////////////////////////////////////////////////////// +// Function: AnalogNode::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +AnalogNode:: +AnalogNode(InputDevice *device) : + DataNode(device->get_name()), + _analog(device) +{ + _xy_output = define_output("xy", EventStoreVec2::get_class_type()); + _xy = new EventStoreVec2(LPoint2(0)); + + nassertv(device != (InputDevice *)NULL); } //////////////////////////////////////////////////////////////////// @@ -74,9 +90,7 @@ write(ostream &out, int indent_level) const { DataNode::write(out, indent_level); if (_analog != (ClientAnalogDevice *)NULL) { - _analog->acquire(); _analog->write_controls(out, indent_level + 2); - _analog->unlock(); } } @@ -100,8 +114,6 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, _analog->poll(); LPoint2 out(0.0f, 0.0f); - - _analog->acquire(); for (int i = 0; i < max_outputs; i++) { if (_outputs[i]._index >= 0 && _analog->is_control_known(_outputs[i]._index)) { @@ -112,7 +124,6 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, } } } - _analog->unlock(); _xy->set_value(out); output.set_data(_xy_output, EventParameter(_xy)); } diff --git a/panda/src/device/analogNode.h b/panda/src/device/analogNode.h index 216c4ae0d4..ea0a6c573e 100644 --- a/panda/src/device/analogNode.h +++ b/panda/src/device/analogNode.h @@ -45,6 +45,7 @@ class EXPCL_PANDA_DEVICE AnalogNode : public DataNode { PUBLISHED: AnalogNode(ClientBase *client, const string &device_name); + AnalogNode(InputDevice *device); virtual ~AnalogNode(); INLINE bool is_valid() const; @@ -73,7 +74,7 @@ private: enum { max_outputs = 2 }; OutputData _outputs[max_outputs]; - PT(ClientAnalogDevice) _analog; + PT(InputDevice) _analog; protected: // Inherited from DataNode diff --git a/panda/src/device/buttonNode.I b/panda/src/device/buttonNode.I index 27faac8325..2eed86eb26 100644 --- a/panda/src/device/buttonNode.I +++ b/panda/src/device/buttonNode.I @@ -21,7 +21,7 @@ //////////////////////////////////////////////////////////////////// INLINE bool ButtonNode:: is_valid() const { - return (_button != (ClientButtonDevice *)NULL) && _button->is_connected(); + return (_device != (ClientButtonDevice *)NULL) && _device->is_connected(); } //////////////////////////////////////////////////////////////////// @@ -36,10 +36,7 @@ is_valid() const { //////////////////////////////////////////////////////////////////// INLINE int ButtonNode:: get_num_buttons() const { - _button->acquire(); - int result = _button->get_num_buttons(); - _button->unlock(); - return result; + return _device->get_num_buttons(); } //////////////////////////////////////////////////////////////////// @@ -58,9 +55,7 @@ get_num_buttons() const { //////////////////////////////////////////////////////////////////// INLINE void ButtonNode:: set_button_map(int index, ButtonHandle button) { - _button->acquire(); - _button->set_button_map(index, button); - _button->unlock(); + _device->set_button_map(index, button); } //////////////////////////////////////////////////////////////////// @@ -73,10 +68,7 @@ set_button_map(int index, ButtonHandle button) { //////////////////////////////////////////////////////////////////// INLINE ButtonHandle ButtonNode:: get_button_map(int index) const { - _button->acquire(); - ButtonHandle result = _button->get_button_map(index); - _button->unlock(); - return result; + return _device->get_button_map(index); } //////////////////////////////////////////////////////////////////// @@ -88,10 +80,7 @@ get_button_map(int index) const { //////////////////////////////////////////////////////////////////// INLINE bool ButtonNode:: get_button_state(int index) const { - _button->acquire(); - bool result = _button->get_button_state(index); - _button->unlock(); - return result; + return _device->get_button_state(index); } //////////////////////////////////////////////////////////////////// @@ -103,8 +92,5 @@ get_button_state(int index) const { //////////////////////////////////////////////////////////////////// INLINE bool ButtonNode:: is_button_known(int index) const { - _button->acquire(); - bool result = _button->is_button_known(index); - _button->unlock(); - return result; + return _device->is_button_known(index); } diff --git a/panda/src/device/buttonNode.cxx b/panda/src/device/buttonNode.cxx index ebae5309a3..d0306b915e 100644 --- a/panda/src/device/buttonNode.cxx +++ b/panda/src/device/buttonNode.cxx @@ -30,7 +30,6 @@ ButtonNode(ClientBase *client, const string &device_name) : DataNode(device_name) { _button_events_output = define_output("button_events", ButtonEventList::get_class_type()); - _button_events = new ButtonEventList; nassertv(client != (ClientBase *)NULL); PT(ClientDevice) device = @@ -49,7 +48,21 @@ ButtonNode(ClientBase *client, const string &device_name) : return; } - _button = DCAST(ClientButtonDevice, device); + _device = device; +} + +//////////////////////////////////////////////////////////////////// +// Function: ButtonNode::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +ButtonNode:: +ButtonNode(InputDevice *device) : + DataNode(device->get_name()), + _device(device) +{ + _button_events_output = define_output("button_events", ButtonEventList::get_class_type()); + _device = device; } //////////////////////////////////////////////////////////////////// @@ -59,7 +72,7 @@ ButtonNode(ClientBase *client, const string &device_name) : //////////////////////////////////////////////////////////////////// ButtonNode:: ~ButtonNode() { - // When the _button pointer destructs, the ClientButtonDevice + // When the _device pointer destructs, the ClientButtonDevice // disconnects itself from the ClientBase, and everything that needs // to get turned off does. Magic. } @@ -73,11 +86,9 @@ void ButtonNode:: output(ostream &out) const { DataNode::output(out); - if (_button != (ClientButtonDevice *)NULL) { + if (_device != (InputDevice *)NULL) { out << " ("; - _button->acquire(); - _button->output_buttons(out); - _button->unlock(); + _device->output_buttons(out); out << ")"; } } @@ -91,10 +102,8 @@ void ButtonNode:: write(ostream &out, int indent_level) const { DataNode::write(out, indent_level); - if (_button != (ClientButtonDevice *)NULL) { - _button->acquire(); - _button->write_buttons(out, indent_level + 2); - _button->unlock(); + if (_device != (InputDevice *)NULL) { + _device->write_buttons(out, indent_level + 2); } } @@ -115,14 +124,7 @@ void ButtonNode:: do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { if (is_valid()) { - _button->poll(); - _button->acquire(); - - (*_button_events) = (*_button->get_button_events()); - - _button->get_button_events()->clear(); - _button->unlock(); - - output.set_data(_button_events_output, EventParameter(_button_events)); + PT(ButtonEventList) bel = _device->get_button_events(); + output.set_data(_button_events_output, EventParameter(bel)); } } diff --git a/panda/src/device/buttonNode.h b/panda/src/device/buttonNode.h index 43f04b954e..349e76e708 100644 --- a/panda/src/device/buttonNode.h +++ b/panda/src/device/buttonNode.h @@ -20,8 +20,6 @@ #include "clientBase.h" #include "clientButtonDevice.h" #include "dataNode.h" -#include "buttonEventList.h" - //////////////////////////////////////////////////////////////////// // Class : ButtonNode @@ -41,6 +39,7 @@ class EXPCL_PANDA_DEVICE ButtonNode : public DataNode { PUBLISHED: ButtonNode(ClientBase *client, const string &device_name); + ButtonNode(InputDevice *device); virtual ~ButtonNode(); INLINE bool is_valid() const; @@ -58,7 +57,7 @@ public: virtual void write(ostream &out, int indent_level = 0) const; private: - PT(ClientButtonDevice) _button; + PT(InputDevice) _device; protected: // Inherited from DataNode @@ -69,7 +68,6 @@ protected: private: // outputs int _button_events_output; - PT(ButtonEventList) _button_events; public: static TypeHandle get_class_type() { diff --git a/panda/src/device/clientAnalogDevice.I b/panda/src/device/clientAnalogDevice.I index c36619d4f1..3877364de9 100644 --- a/panda/src/device/clientAnalogDevice.I +++ b/panda/src/device/clientAnalogDevice.I @@ -13,18 +13,6 @@ //////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::AnalogState::Constructor -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE ClientAnalogDevice::AnalogState:: -AnalogState() : - _state(0.0), - _known(false) -{ -} - //////////////////////////////////////////////////////////////////// // Function: ClientAnalogDevice::Constructor // Access: Protected @@ -35,65 +23,3 @@ ClientAnalogDevice(ClientBase *client, const string &device_name): ClientDevice(client, get_class_type(), device_name) { } - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::get_num_controls -// Access: Public -// Description: Returns the number of analog controls known to the -// ClientAnalogDevice. This number may change as -// more controls are discovered. -//////////////////////////////////////////////////////////////////// -INLINE int ClientAnalogDevice:: -get_num_controls() const { - return _controls.size(); -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::set_control_state -// Access: Public -// Description: Sets the state of the indicated analog index. The -// caller should ensure that acquire() is in effect while -// this call is made. This should be a number in the -// range -1.0 to 1.0, representing the current position -// of the control within its total range of movement. -//////////////////////////////////////////////////////////////////// -INLINE void ClientAnalogDevice:: -set_control_state(int index, double state) { - ensure_control_index(index); - nassertv(index >= 0 && index < (int)_controls.size()); - _controls[index]._state = state; - _controls[index]._known = true; -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::get_control_state -// Access: Public -// Description: Returns the current position of indicated analog -// control (identified by its index number), or 0.0 if -// the control is unknown. The normal range of a single -// control is -1.0 to 1.0. -//////////////////////////////////////////////////////////////////// -INLINE double ClientAnalogDevice:: -get_control_state(int index) const { - if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._state; - } else { - return 0.0; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::is_control_known -// Access: Public -// Description: Returns true if the state of the indicated analog -// control is known, or false if we have never heard -// anything about this particular control. -//////////////////////////////////////////////////////////////////// -INLINE bool ClientAnalogDevice:: -is_control_known(int index) const { - if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._known; - } else { - return false; - } -} diff --git a/panda/src/device/clientAnalogDevice.cxx b/panda/src/device/clientAnalogDevice.cxx index 1dbe277c82..6d4a84de23 100644 --- a/panda/src/device/clientAnalogDevice.cxx +++ b/panda/src/device/clientAnalogDevice.cxx @@ -19,25 +19,6 @@ TypeHandle ClientAnalogDevice::_type_handle; - - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::ensure_control_index -// Access: Private -// Description: Guarantees that there is a slot in the array for the -// indicated index number, by filling the array up to -// that index if necessary. -//////////////////////////////////////////////////////////////////// -void ClientAnalogDevice:: -ensure_control_index(int index) { - nassertv(index >= 0); - - _controls.reserve(index + 1); - while ((int)_controls.size() <= index) { - _controls.push_back(AnalogState()); - } -} - //////////////////////////////////////////////////////////////////// // Function: ClientAnalogDevice::write // Access: Public, Virtual @@ -45,32 +26,6 @@ ensure_control_index(int index) { //////////////////////////////////////////////////////////////////// void ClientAnalogDevice:: write(ostream &out, int indent_level) const { - indent(out, indent_level) << get_type() << " " << get_device_name() << ":\n"; + indent(out, indent_level) << get_type() << " " << get_name() << ":\n"; write_controls(out, indent_level + 2); } - -//////////////////////////////////////////////////////////////////// -// Function: ClientAnalogDevice::write_analogs -// Access: Public -// Description: Writes a multi-line description of the current analog -// control states. -//////////////////////////////////////////////////////////////////// -void ClientAnalogDevice:: -write_controls(ostream &out, int indent_level) const { - bool any_controls = false; - Controls::const_iterator ai; - for (ai = _controls.begin(); ai != _controls.end(); ++ai) { - const AnalogState &state = (*ai); - if (state._known) { - any_controls = true; - - indent(out, indent_level) - << (int)(ai - _controls.begin()) << ". " << state._state << "\n"; - } - } - - if (!any_controls) { - indent(out, indent_level) - << "(no known analog controls)\n"; - } -} diff --git a/panda/src/device/clientAnalogDevice.h b/panda/src/device/clientAnalogDevice.h index 4db8fa53c4..c79daa8b16 100644 --- a/panda/src/device/clientAnalogDevice.h +++ b/panda/src/device/clientAnalogDevice.h @@ -36,30 +36,7 @@ protected: INLINE ClientAnalogDevice(ClientBase *client, const string &device_name); public: - INLINE int get_num_controls() const; - - INLINE void set_control_state(int index, double state); - INLINE double get_control_state(int index) const; - INLINE bool is_control_known(int index) const; - virtual void write(ostream &out, int indent_level = 0) const; - void write_controls(ostream &out, int indent_level) const; - -private: - void ensure_control_index(int index); - -protected: - class AnalogState { - public: - INLINE AnalogState(); - - double _state; - bool _known; - }; - - typedef pvector Controls; - Controls _controls; - public: static TypeHandle get_class_type() { diff --git a/panda/src/device/clientBase.cxx b/panda/src/device/clientBase.cxx index e01c35766b..12c570c28c 100644 --- a/panda/src/device/clientBase.cxx +++ b/panda/src/device/clientBase.cxx @@ -144,7 +144,6 @@ get_device(TypeHandle device_type, const string &device_name) { if (device != (ClientDevice *)NULL) { dbn.insert(DevicesByName::value_type(device_name, device)); - device->_is_connected = true; } return device; diff --git a/panda/src/device/clientButtonDevice.I b/panda/src/device/clientButtonDevice.I index 21308006c9..e039e28421 100644 --- a/panda/src/device/clientButtonDevice.I +++ b/panda/src/device/clientButtonDevice.I @@ -12,113 +12,3 @@ // //////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::ButtonState::Constructor -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE ClientButtonDevice::ButtonState:: -ButtonState() : - _handle(ButtonHandle::none()), - _state(S_unknown) -{ -} - - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::get_num_buttons -// Access: Public -// Description: Returns the number of buttons known to the -// ClientButtonDevice. This includes those buttons -// whose state has been seen, as well as buttons that -// have been associated with a ButtonHandle even if -// their state is unknown. This number may change as -// more buttons are discovered. -//////////////////////////////////////////////////////////////////// -INLINE int ClientButtonDevice:: -get_num_buttons() const { - return _buttons.size(); -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::set_button_map -// Access: Public -// Description: Associates the indicated ButtonHandle with the button -// of the indicated index number. When the given button -// index changes state, a corresponding ButtonEvent will -// be generated with the given ButtonHandle. Pass -// ButtonHandle::none() to turn off any association. -// -// It is not necessary to call this if you simply want -// to query the state of the various buttons by index -// number; this is only necessary in order to generate -// ButtonEvents when the buttons change state. -//////////////////////////////////////////////////////////////////// -INLINE void ClientButtonDevice:: -set_button_map(int index, ButtonHandle button) { - ensure_button_index(index); - nassertv(index >= 0 && index < (int)_buttons.size()); - _buttons[index]._handle = button; -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::get_button_map -// Access: Public -// Description: Returns the ButtonHandle that was previously -// associated with the given index number by -// a call to set_button_map(), or ButtonHandle::none() -// if no button was associated. -//////////////////////////////////////////////////////////////////// -INLINE ButtonHandle ClientButtonDevice:: -get_button_map(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { - return _buttons[index]._handle; - } else { - return ButtonHandle::none(); - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::get_button_state -// Access: Public -// Description: Returns true if the indicated button (identified by -// its index number) is currently known to be down, or -// false if it is up or unknown. -//////////////////////////////////////////////////////////////////// -INLINE bool ClientButtonDevice:: -get_button_state(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { - return (_buttons[index]._state == S_down); - } else { - return false; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::is_button_known -// Access: Public -// Description: Returns true if the state of the indicated button is -// known, or false if we have never heard anything about -// this particular button. -//////////////////////////////////////////////////////////////////// -INLINE bool ClientButtonDevice:: -is_button_known(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { - return _buttons[index]._state != S_unknown; - } else { - return false; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::get_button_events -// Access: Public -// Description: Returns the list of recently-generated ButtonEvents. -// This must be periodically cleared, or the buttons -// will accumulate. -//////////////////////////////////////////////////////////////////// -INLINE ButtonEventList *ClientButtonDevice:: -get_button_events() const { - return _button_events; -} diff --git a/panda/src/device/clientButtonDevice.cxx b/panda/src/device/clientButtonDevice.cxx index e45561ac5a..02980c55b1 100644 --- a/panda/src/device/clientButtonDevice.cxx +++ b/panda/src/device/clientButtonDevice.cxx @@ -28,47 +28,6 @@ ClientButtonDevice:: ClientButtonDevice(ClientBase *client, const string &device_name): ClientDevice(client, get_class_type(), device_name) { - _button_events = new ButtonEventList(); -} - - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::set_button_state -// Access: Public -// Description: Sets the state of the indicated button index, where -// true indicates down, and false indicates up. This -// may generate a ButtonEvent if the button has an -// associated ButtonHandle. The caller should ensure -// that acquire() is in effect while this call is made. -//////////////////////////////////////////////////////////////////// -void ClientButtonDevice:: -set_button_state(int index, bool down) { - ensure_button_index(index); - nassertv(index >= 0 && index < (int)_buttons.size()); - _buttons[index]._state = down ? S_down : S_up; - - ButtonHandle handle = _buttons[index]._handle; - if (handle != ButtonHandle::none()) { - _button_events->add_event(ButtonEvent(handle, down ? ButtonEvent::T_down : ButtonEvent::T_up)); - } -} - - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::ensure_button_index -// Access: Private -// Description: Guarantees that there is a slot in the array for the -// indicated index number, by filling the array up to -// that index if necessary. -//////////////////////////////////////////////////////////////////// -void ClientButtonDevice:: -ensure_button_index(int index) { - nassertv(index >= 0); - - _buttons.reserve(index + 1); - while ((int)_buttons.size() <= index) { - _buttons.push_back(ButtonState()); - } } //////////////////////////////////////////////////////////////////// @@ -78,7 +37,7 @@ ensure_button_index(int index) { //////////////////////////////////////////////////////////////////// void ClientButtonDevice:: output(ostream &out) const { - out << get_type() << " " << get_device_name() << " ("; + out << get_type() << " " << get_name() << " ("; output_buttons(out); out << ")"; } @@ -90,74 +49,6 @@ output(ostream &out) const { //////////////////////////////////////////////////////////////////// void ClientButtonDevice:: write(ostream &out, int indent_level) const { - indent(out, indent_level) << get_type() << " " << get_device_name() << ":\n"; + indent(out, indent_level) << get_type() << " " << get_name() << ":\n"; write_buttons(out, indent_level + 2); } - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::output_buttons -// Access: Public -// Description: Writes a one-line string of all of the current button -// states. -//////////////////////////////////////////////////////////////////// -void ClientButtonDevice:: -output_buttons(ostream &out) const { - bool any_buttons = false; - Buttons::const_iterator bi; - for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { - const ButtonState &state = (*bi); - if (state._state != S_unknown) { - if (any_buttons) { - out << ", "; - } - any_buttons = true; - out << (int)(bi - _buttons.begin()) << "="; - if (state._state == S_up) { - out << "up"; - } else { - out << "down"; - } - } - } - - if (!any_buttons) { - out << "no known buttons"; - } -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientButtonDevice::write_buttons -// Access: Public -// Description: Writes a multi-line description of the current button -// states. -//////////////////////////////////////////////////////////////////// -void ClientButtonDevice:: -write_buttons(ostream &out, int indent_level) const { - bool any_buttons = false; - Buttons::const_iterator bi; - for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { - const ButtonState &state = (*bi); - if (state._state != S_unknown) { - any_buttons = true; - - indent(out, indent_level) - << (int)(bi - _buttons.begin()) << ". "; - - if (state._handle != ButtonHandle::none()) { - out << "(" << state._handle << ") "; - } - - if (state._state == S_up) { - out << "up"; - } else { - out << "down"; - } - out << "\n"; - } - } - - if (!any_buttons) { - indent(out, indent_level) - << "(no known buttons)\n"; - } -} diff --git a/panda/src/device/clientButtonDevice.h b/panda/src/device/clientButtonDevice.h index b9643d26c2..3deee23cbe 100644 --- a/panda/src/device/clientButtonDevice.h +++ b/panda/src/device/clientButtonDevice.h @@ -39,46 +39,9 @@ protected: ClientButtonDevice(ClientBase *client, const string &device_name); public: - INLINE int get_num_buttons() const; - - INLINE void set_button_map(int index, ButtonHandle button); - INLINE ButtonHandle get_button_map(int index) const; - - void set_button_state(int index, bool down); - INLINE bool get_button_state(int index) const; - INLINE bool is_button_known(int index) const; - - INLINE ButtonEventList *get_button_events() const; - virtual void output(ostream &out) const; virtual void write(ostream &out, int indent_level = 0) const; - void output_buttons(ostream &out) const; - void write_buttons(ostream &out, int indent_level) const; - -private: - void ensure_button_index(int index); - -protected: - enum State { - S_unknown, - S_up, - S_down - }; - - class ButtonState { - public: - INLINE ButtonState(); - - ButtonHandle _handle; - State _state; - }; - - typedef pvector Buttons; - Buttons _buttons; - - PT(ButtonEventList) _button_events; - public: static TypeHandle get_class_type() { return _type_handle; @@ -95,7 +58,6 @@ public: private: static TypeHandle _type_handle; - friend class ButtonState; }; #include "clientButtonDevice.I" diff --git a/panda/src/device/clientDevice.I b/panda/src/device/clientDevice.I index efcb92e050..d867978684 100644 --- a/panda/src/device/clientDevice.I +++ b/panda/src/device/clientDevice.I @@ -24,17 +24,6 @@ get_client() const { return _client; } -//////////////////////////////////////////////////////////////////// -// Function: ClientDevice::is_connected -// Access: Public -// Description: Returns true if the device is still connected to its -// ClientBase, false otherwise. -//////////////////////////////////////////////////////////////////// -INLINE bool ClientDevice:: -is_connected() const { - return _is_connected; -} - //////////////////////////////////////////////////////////////////// // Function: ClientDevice::get_device_type // Access: Public @@ -50,45 +39,3 @@ INLINE TypeHandle ClientDevice:: get_device_type() const { return _device_type; } - -//////////////////////////////////////////////////////////////////// -// Function: ClientDevice::get_device_name -// Access: Public -// Description: Returns the device name reported to the ClientBase. -// This has some implementation-defined meaning to -// identify particular devices. -//////////////////////////////////////////////////////////////////// -INLINE const string &ClientDevice:: -get_device_name() const { - return _device_name; -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientDevice::acquire -// Access: Public -// Description: Grabs the mutex associated with this particular -// device. The device will not update asynchronously -// while the mutex is held, allowing the user to copy -// the data out without fear of getting a partial update -// during the copy. -//////////////////////////////////////////////////////////////////// -INLINE void ClientDevice:: -acquire() { -#ifdef OLD_HAVE_IPC - _lock.acquire(); -#endif -} - -//////////////////////////////////////////////////////////////////// -// Function: ClientDevice::unlock -// Access: Public -// Description: Releases the mutex associated with this particular -// device. This should be called after all the data has -// been successfully copied out. See acquire(). -//////////////////////////////////////////////////////////////////// -INLINE void ClientDevice:: -unlock() { -#ifdef OLD_HAVE_IPC - _lock.unlock(); -#endif -} diff --git a/panda/src/device/clientDevice.cxx b/panda/src/device/clientDevice.cxx index d7f7aa90f4..c0ee85d266 100644 --- a/panda/src/device/clientDevice.cxx +++ b/panda/src/device/clientDevice.cxx @@ -27,15 +27,14 @@ TypeHandle ClientDevice::_type_handle; //////////////////////////////////////////////////////////////////// ClientDevice:: ClientDevice(ClientBase *client, TypeHandle device_type, - const string &device_name) : + const string &device_name, int device_flags) : + InputDevice(device_name, DC_unknown, device_flags), _client(client), - _device_type(device_type), - _device_name(device_name) + _device_type(device_type) { // We have to explicitly ref the client pointer, since we can't use // a PT(ClientBase) for circular include reasons. _client->ref(); - _is_connected = false; } //////////////////////////////////////////////////////////////////// @@ -49,7 +48,7 @@ ClientDevice(ClientBase *client, TypeHandle device_type, //////////////////////////////////////////////////////////////////// ClientDevice:: ~ClientDevice() { - nassertv(!_is_connected); + nassertv(!is_connected()); // And now we explicitly unref the client pointer. unref_delete(_client); @@ -72,19 +71,17 @@ ClientDevice:: //////////////////////////////////////////////////////////////////// void ClientDevice:: disconnect() { - if (_is_connected) { - acquire(); + if (is_connected()) { bool disconnected = - _client->disconnect_device(_device_type, _device_name, this); - _is_connected = false; - unlock(); + _client->disconnect_device(_device_type, get_name(), this); + set_connected(false); nassertv(disconnected); } } //////////////////////////////////////////////////////////////////// -// Function: ClientDevice::poll -// Access: Public +// Function: ClientDevice::do_poll +// Access: Public, Virtual, Final // Description: Causes the connected ClientBase to poll all of its // clients, if necessary. This will be a no-op if the // client is running in forked mode, or if it has @@ -94,7 +91,7 @@ disconnect() { // data in this ClientDevice to ensure that it is fresh. //////////////////////////////////////////////////////////////////// void ClientDevice:: -poll() { +do_poll() { _client->poll(); } @@ -105,7 +102,7 @@ poll() { //////////////////////////////////////////////////////////////////// void ClientDevice:: output(ostream &out) const { - out << get_type() << " " << get_device_name(); + out << get_type() << " " << get_name(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/device/clientDevice.h b/panda/src/device/clientDevice.h index b0dbb116e3..3e040bc742 100644 --- a/panda/src/device/clientDevice.h +++ b/panda/src/device/clientDevice.h @@ -16,12 +16,7 @@ #define CLIENTDEVICE_H #include "pandabase.h" - -#include "typedReferenceCount.h" - -#ifdef OLD_HAVE_IPC -#include -#endif +#include "inputDevice.h" class ClientBase; @@ -32,24 +27,20 @@ class ClientBase; // This is an abstract interface; the actual // implementations are in ClientTrackerDevice, etc. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA_DEVICE ClientDevice : public TypedReferenceCount { +class EXPCL_PANDA_DEVICE ClientDevice : public InputDevice { protected: ClientDevice(ClientBase *client, TypeHandle device_type, - const string &device_name); + const string &device_name, int device_flags=0); public: virtual ~ClientDevice(); INLINE ClientBase *get_client() const; INLINE TypeHandle get_device_type() const; - INLINE const string &get_device_name() const; - INLINE bool is_connected() const; void disconnect(); - void poll(); - INLINE void acquire(); - INLINE void unlock(); + virtual void do_poll() FINAL; virtual void output(ostream &out) const; virtual void write(ostream &out, int indent_level = 0) const; @@ -57,21 +48,15 @@ public: private: ClientBase *_client; TypeHandle _device_type; - string _device_name; - bool _is_connected; - -#ifdef OLD_HAVE_IPC - mutex _lock; -#endif public: static TypeHandle get_class_type() { return _type_handle; } static void init_type() { - TypedReferenceCount::init_type(); + InputDevice::init_type(); register_type(_type_handle, "ClientDevice", - TypedReferenceCount::get_class_type()); + InputDevice::get_class_type()); } virtual TypeHandle get_type() const { return get_class_type(); diff --git a/panda/src/device/clientTrackerDevice.I b/panda/src/device/clientTrackerDevice.I index 938ac26922..1b9047c10f 100644 --- a/panda/src/device/clientTrackerDevice.I +++ b/panda/src/device/clientTrackerDevice.I @@ -20,18 +20,6 @@ //////////////////////////////////////////////////////////////////// INLINE ClientTrackerDevice:: ClientTrackerDevice(ClientBase *client, const string &device_name): - ClientDevice(client, get_class_type(), device_name) + ClientDevice(client, get_class_type(), device_name, IDF_has_tracker) { } - -//////////////////////////////////////////////////////////////////// -// Function: ClientTrackerDevice::get_data -// Access: Public -// Description: Returns the TrackerData that this device is -// reporting. -//////////////////////////////////////////////////////////////////// -INLINE const TrackerData &ClientTrackerDevice:: -get_data() const { - return _data; -} - diff --git a/panda/src/device/clientTrackerDevice.h b/panda/src/device/clientTrackerDevice.h index 8fbfd00c55..04123ca0f4 100644 --- a/panda/src/device/clientTrackerDevice.h +++ b/panda/src/device/clientTrackerDevice.h @@ -30,12 +30,6 @@ class EXPCL_PANDA_DEVICE ClientTrackerDevice : public ClientDevice { protected: INLINE ClientTrackerDevice(ClientBase *client, const string &device_name); -public: - INLINE const TrackerData &get_data() const; - -protected: - TrackerData _data; - public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/device/config_device.cxx b/panda/src/device/config_device.cxx index a86986ed05..84e290be91 100644 --- a/panda/src/device/config_device.cxx +++ b/panda/src/device/config_device.cxx @@ -23,7 +23,9 @@ #include "clientDialDevice.h" #include "clientTrackerDevice.h" #include "dialNode.h" -#include "mouseAndKeyboard.h" +#include "evdevInputDevice.h" +#include "inputDevice.h" +#include "linuxJoystickDevice.h" #include "trackerNode.h" #include "virtualMouse.h" @@ -64,7 +66,12 @@ init_libdevice() { ClientDialDevice::init_type(); ClientTrackerDevice::init_type(); DialNode::init_type(); - MouseAndKeyboard::init_type(); + InputDevice::init_type(); TrackerNode::init_type(); VirtualMouse::init_type(); + +#ifdef PHAVE_LINUX_INPUT_H + EvdevInputDevice::init_type(); + LinuxJoystickDevice::init_type(); +#endif } diff --git a/panda/src/device/dialNode.I b/panda/src/device/dialNode.I index bd04635e46..ab101901b7 100644 --- a/panda/src/device/dialNode.I +++ b/panda/src/device/dialNode.I @@ -33,10 +33,7 @@ is_valid() const { //////////////////////////////////////////////////////////////////// INLINE int DialNode:: get_num_dials() const { - _dial->acquire(); - int result = _dial->get_num_dials(); - _dial->unlock(); - return result; + return _dial->get_num_dials(); } //////////////////////////////////////////////////////////////////// @@ -49,10 +46,7 @@ get_num_dials() const { //////////////////////////////////////////////////////////////////// INLINE double DialNode:: read_dial(int index) { - _dial->acquire(); - double result = _dial->read_dial(index); - _dial->unlock(); - return result; + return _dial->read_dial(index); } //////////////////////////////////////////////////////////////////// @@ -64,8 +58,5 @@ read_dial(int index) { //////////////////////////////////////////////////////////////////// INLINE bool DialNode:: is_dial_known(int index) const { - _dial->acquire(); - bool result = _dial->is_dial_known(index); - _dial->unlock(); - return result; + return _dial->is_dial_known(index); } diff --git a/panda/src/device/evdevInputDevice.I b/panda/src/device/evdevInputDevice.I new file mode 100644 index 0000000000..9e702ad1d8 --- /dev/null +++ b/panda/src/device/evdevInputDevice.I @@ -0,0 +1,14 @@ +// Filename: evdevInputDevice.I +// Created by: rdb (24Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx new file mode 100644 index 0000000000..e03934d658 --- /dev/null +++ b/panda/src/device/evdevInputDevice.cxx @@ -0,0 +1,424 @@ +// Filename: evdevInputDevice.cxx +// Created by: rdb (24Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "evdevInputDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +#include "gamepadButton.h" +#include "keyboardButton.h" +#include "mouseButton.h" + +#include +#include + +#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7))) + +TypeHandle EvdevInputDevice::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::Constructor +// Access: Published +// Description: Creates a new device using the Linux joystick +// device using the given file descriptor. It will +// be closed when this object destructs. +//////////////////////////////////////////////////////////////////// +EvdevInputDevice:: +EvdevInputDevice(int fd) : + _fd(fd) { + init_device(); +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::Constructor +// Access: Published +// Description: Creates a new device using the Linux joystick +// device using the given device filename. +//////////////////////////////////////////////////////////////////// +EvdevInputDevice:: +EvdevInputDevice(const string &fn) { + _fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK); + + if (_fd >= 0) { + init_device(); + } else { + _is_connected = false; + device_cat.error() + << "Opening raw input device: " << strerror(errno) << " " << fn << "\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +EvdevInputDevice:: +~EvdevInputDevice() { + if (_fd != -1) { + close(_fd); + _fd = -1; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::do_poll +// Access: Private, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void EvdevInputDevice:: +do_poll() { + if (_fd != -1) { + while (process_events()) {} + } +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::init_device +// Access: Private +// Description: Reads basic properties from the device. +//////////////////////////////////////////////////////////////////// +bool EvdevInputDevice:: +init_device() { + uint8_t evtypes[EV_MAX/8 + 1]; + char name[256]; + char uniq[256]; + if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || + ioctl(_fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0 || + ioctl(_fd, EVIOCGBIT(0, EV_MAX), &evtypes) < 0) { + close(_fd); + _fd = -1; + _is_connected = false; + device_cat.error() << "Opening raw input device: ioctl failed\n"; + return false; + } + + for (char *p=name; *p; p++) { + if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { + *p = '_'; + } + } + for (char *p=uniq; *p; p++) { + if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { + *p = '_'; + } + } + + _name = ((string)name) + "." + uniq; + + if (test_bit(EV_REL, evtypes)) { + _flags |= IDF_has_pointer; + } + + if (test_bit(EV_FF, evtypes)) { + _flags |= IDF_has_vibration; + } + + _is_connected = true; + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::process_events +// Access: Private +// Description: Reads a number of events from the device. +//////////////////////////////////////////////////////////////////// +bool EvdevInputDevice:: +process_events() { + // Read 8 events at a time. + struct input_event events[8]; + + int n_read = read(_fd, events, sizeof(events)); + if (n_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available for now. + + } else if (errno == ENODEV || errno == EINVAL) { + // The device ceased to exist, so we better close it. + close(_fd); + _fd = -1; + _is_connected = false; + errno = 0; + + } else { + device_cat.error() << "read: " << strerror(errno) << "\n"; + } + return false; + } + + if (n_read == 0) { + return false; + } + + n_read /= sizeof(struct input_event); + + int x = _pointer_data.get_x(); + int y = _pointer_data.get_y(); + double time = ClockObject::get_global_clock()->get_frame_time(); + ButtonHandle button; + + for (int i = 0; i < n_read; ++i) { + switch (events[i].type) { + case EV_SYN: + break; + + case EV_REL: + if (events[i].code == REL_X) x += events[i].value; + if (events[i].code == REL_Y) y += events[i].value; + break; + + case EV_ABS: + if (events[i].code == ABS_X) x = events[i].value; + if (events[i].code == ABS_Y) y = events[i].value; + break; + + case EV_KEY: + button = map_button(events[i].code); + _button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time)); + break; + + default: + //cerr << "event " << events[i].type << " - " << events[i].code << " - " << events[i].value << "\n"; + break; + } + } + + set_pointer(true, x, y, time); + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::map_button +// Access: Public, Static +// Description: Maps an evdev code to a ButtonHandle. +//////////////////////////////////////////////////////////////////// +ButtonHandle EvdevInputDevice:: +map_button(int code) { + if (code >= 0 && code < 0x80) { + // See linux/input.h for the source of this mapping. + static const ButtonHandle keyboard_map[] = { + ButtonHandle::none(), + KeyboardButton::escape(), + KeyboardButton::ascii_key('1'), + KeyboardButton::ascii_key('2'), + KeyboardButton::ascii_key('3'), + KeyboardButton::ascii_key('4'), + KeyboardButton::ascii_key('5'), + KeyboardButton::ascii_key('6'), + KeyboardButton::ascii_key('7'), + KeyboardButton::ascii_key('8'), + KeyboardButton::ascii_key('9'), + KeyboardButton::ascii_key('0'), + KeyboardButton::ascii_key('-'), + KeyboardButton::ascii_key('='), + KeyboardButton::backspace(), + KeyboardButton::tab(), + KeyboardButton::ascii_key('q'), + KeyboardButton::ascii_key('w'), + KeyboardButton::ascii_key('e'), + KeyboardButton::ascii_key('r'), + KeyboardButton::ascii_key('t'), + KeyboardButton::ascii_key('y'), + KeyboardButton::ascii_key('u'), + KeyboardButton::ascii_key('i'), + KeyboardButton::ascii_key('o'), + KeyboardButton::ascii_key('p'), + KeyboardButton::ascii_key('['), + KeyboardButton::ascii_key(']'), + KeyboardButton::enter(), + KeyboardButton::lcontrol(), + KeyboardButton::ascii_key('a'), + KeyboardButton::ascii_key('s'), + KeyboardButton::ascii_key('d'), + KeyboardButton::ascii_key('f'), + KeyboardButton::ascii_key('g'), + KeyboardButton::ascii_key('h'), + KeyboardButton::ascii_key('j'), + KeyboardButton::ascii_key('k'), + KeyboardButton::ascii_key('l'), + KeyboardButton::ascii_key(';'), + KeyboardButton::ascii_key('\''), + KeyboardButton::ascii_key('`'), + KeyboardButton::lshift(), + KeyboardButton::ascii_key('\\'), + KeyboardButton::ascii_key('z'), + KeyboardButton::ascii_key('x'), + KeyboardButton::ascii_key('c'), + KeyboardButton::ascii_key('v'), + KeyboardButton::ascii_key('b'), + KeyboardButton::ascii_key('n'), + KeyboardButton::ascii_key('m'), + KeyboardButton::ascii_key(','), + KeyboardButton::ascii_key('.'), + KeyboardButton::ascii_key('/'), + KeyboardButton::rshift(), + KeyboardButton::ascii_key('*'), + KeyboardButton::lalt(), + KeyboardButton::space(), + KeyboardButton::caps_lock(), + KeyboardButton::f1(), + KeyboardButton::f2(), + KeyboardButton::f3(), + KeyboardButton::f4(), + KeyboardButton::f5(), + KeyboardButton::f6(), + KeyboardButton::f7(), + KeyboardButton::f8(), + KeyboardButton::f9(), + KeyboardButton::f10(), + KeyboardButton::num_lock(), + KeyboardButton::scroll_lock(), + KeyboardButton::ascii_key('7'), + KeyboardButton::ascii_key('8'), + KeyboardButton::ascii_key('9'), + KeyboardButton::ascii_key('-'), + KeyboardButton::ascii_key('4'), + KeyboardButton::ascii_key('5'), + KeyboardButton::ascii_key('6'), + KeyboardButton::ascii_key('+'), + KeyboardButton::ascii_key('1'), + KeyboardButton::ascii_key('2'), + KeyboardButton::ascii_key('3'), + KeyboardButton::ascii_key('0'), + KeyboardButton::ascii_key('.'), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + KeyboardButton::f11(), + KeyboardButton::f12(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + KeyboardButton::enter(), + KeyboardButton::rcontrol(), + KeyboardButton::ascii_key('/'), + KeyboardButton::print_screen(), + KeyboardButton::ralt(), + ButtonHandle::none(), + KeyboardButton::home(), + KeyboardButton::up(), + KeyboardButton::page_up(), + KeyboardButton::left(), + KeyboardButton::right(), + KeyboardButton::end(), + KeyboardButton::down(), + KeyboardButton::page_down(), + KeyboardButton::insert(), + KeyboardButton::del(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + KeyboardButton::pause(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + ButtonHandle::none(), + KeyboardButton::lmeta(), + KeyboardButton::rmeta(), + KeyboardButton::menu(), + }; + return keyboard_map[code]; + + } else if (code < 0x100) { + return ButtonHandle::none(); + + } else if ((code & 0xfff0) == BTN_MOUSE) { + // The number for these is reversed in Panda. + if (code == BTN_RIGHT) { + return MouseButton::three(); + } else if (code == BTN_MIDDLE) { + return MouseButton::two(); + } else { + return MouseButton::button(code - BTN_MOUSE); + } + } + + switch (code) { + case BTN_A: + return GamepadButton::action_a(); + + case BTN_B: + return GamepadButton::action_b(); + + case BTN_C: + return GamepadButton::action_c(); + + case BTN_X: + return GamepadButton::action_x(); + + case BTN_Y: + return GamepadButton::action_y(); + + case BTN_Z: + return GamepadButton::action_z(); + + case BTN_TL: + return GamepadButton::lshoulder(); + + case BTN_TR: + return GamepadButton::rshoulder(); + + case BTN_TL2: + return GamepadButton::ltrigger(); + + case BTN_TR2: + return GamepadButton::rtrigger(); + + case BTN_SELECT: + return GamepadButton::back(); + + case BTN_START: + return GamepadButton::start(); + + case BTN_MODE: + return GamepadButton::guide(); + + case BTN_THUMBL: + return GamepadButton::lstick(); + + case BTN_THUMBR: + return GamepadButton::rstick(); + + case BTN_TRIGGER_HAPPY1: + return GamepadButton::dpad_left(); + + case BTN_TRIGGER_HAPPY2: + return GamepadButton::dpad_right(); + + case BTN_TRIGGER_HAPPY3: + return GamepadButton::dpad_up(); + + case BTN_TRIGGER_HAPPY4: + return GamepadButton::dpad_down(); + + default: + return ButtonHandle::none(); + } +} + +#endif diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h new file mode 100644 index 0000000000..d3d4203625 --- /dev/null +++ b/panda/src/device/evdevInputDevice.h @@ -0,0 +1,63 @@ +// Filename: evdevInputDevice.h +// Created by: rdb (24Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef EVDEVINPUTDEVICE_H +#define EVDEVINPUTDEVICE_H + +#include "inputDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +//////////////////////////////////////////////////////////////////// +// Class : EvdevInputDevice +// Description : This is a type of device that uses the Linux +// /dev/input/event# API to read data from a raw mouse. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { +PUBLISHED: + EvdevInputDevice(int fd); + EvdevInputDevice(const string &device); + virtual ~EvdevInputDevice(); + +private: + virtual void do_poll(); + + bool init_device(); + bool process_events(); + +private: + int _fd; + +public: + static ButtonHandle map_button(int code); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + InputDevice::init_type(); + register_type(_type_handle, "EvdevInputDevice", + InputDevice::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "evdevInputDevice.I" + +#endif // PHAVE_LINUX_INPUT_H + +#endif // EVDEVINPUTDEVICE_H diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I new file mode 100644 index 0000000000..99962b7750 --- /dev/null +++ b/panda/src/device/inputDevice.I @@ -0,0 +1,445 @@ +// Filename: inputDevice.I +// Created by: drose (24May00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Default Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE InputDevice:: +InputDevice() : + _flags(0), + _device_class(DC_unknown), + _is_connected(false), + _event_sequence(0), + _enable_pointer_events(false), + _battery_level(-1), + _max_battery_level(-1) +{ + _button_events = new ButtonEventList; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_name +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE string InputDevice:: +get_name() const { + LightMutexHolder holder(_lock); + return _name; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::is_connected +// Access: Public +// Description: Returns true if the device is still connected and +// able to receive data, false otherwise. May return +// false positives. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +is_connected() const { + LightMutexHolder holder(_lock); + return _is_connected; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_device_class +// Access: Public +// Description: Returns an identification of the general type of +// device. If this could not be determined, returns +// DC_unknown. +//////////////////////////////////////////////////////////////////// +INLINE InputDevice::DeviceClass InputDevice:: +get_device_class() const { + LightMutexHolder holder(_lock); + return _device_class; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_pointer +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +has_pointer() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_pointer) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_keyboard +// Access: Public +// Description: Returns true if the device has a physical keyboard +// designed for text entry. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +has_keyboard() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_keyboard) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_tracker +// Access: Public +// Description: Returns true if the device features a tracker that +// can track position and/or orientation in 3D space. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +has_tracker() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_tracker) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_battery +// Access: Public +// Description: Returns true if the device may be able to provide +// information about its battery life. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +has_battery() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_battery) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_axis +// Access: Public +// Description: Returns true if the device has the given axis. +//////////////////////////////////////////////////////////////////// +/* +INLINE bool InputDevice:: +has_axis(Axis axis) const { + LightMutexHolder holder(_lock); + return ((_has_axes & (1 << axis)) != 0); +}*/ + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_pointer +// Access: Public +// Description: Returns the PointerData associated with the input +// device's pointer. This only makes sense if +// has_pointer() also returns true. +//////////////////////////////////////////////////////////////////// +INLINE PointerData InputDevice:: +get_pointer() const { + LightMutexHolder holder(_lock); + return _pointer_data; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_tracker +// Access: Public +// Description: Returns the TrackerData associated with the input +// device's tracker. This only makes sense if +// has_tracker() also returns true. +//////////////////////////////////////////////////////////////////// +INLINE TrackerData InputDevice:: +get_tracker() const { + LightMutexHolder holder(_lock); + return _tracker_data; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_battery_level +// Access: Public +// Description: Returns a rough indication of the battery level, +// ranging from 0 (completely empty battery) to the +// number reported by get_max_battery_level(), which +// represents a full battery. +// If this information is not known, returns -1. +//////////////////////////////////////////////////////////////////// +INLINE short InputDevice:: +get_battery_level() const { + LightMutexHolder holder(_lock); + return _battery_level; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_max_battery_level +// Access: Public +// Description: Returns the maximum value that may be reported by +// get_battery_level(), representing a full battery. +// Returns -1 if no battery information is known. +//////////////////////////////////////////////////////////////////// +INLINE short InputDevice:: +get_max_battery_level() const { + LightMutexHolder holder(_lock); + return _max_battery_level; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_num_buttons +// Access: Public +// Description: Returns the number of buttons known to the +// ClientButtonDevice. This includes those buttons +// whose state has been seen, as well as buttons that +// have been associated with a ButtonHandle even if +// their state is unknown. This number may change as +// more buttons are discovered. +//////////////////////////////////////////////////////////////////// +INLINE int InputDevice:: +get_num_buttons() const { + LightMutexHolder holder(_lock); + return _buttons.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_button_map +// Access: Public +// Description: Associates the indicated ButtonHandle with the button +// of the indicated index number. When the given button +// index changes state, a corresponding ButtonEvent will +// be generated with the given ButtonHandle. Pass +// ButtonHandle::none() to turn off any association. +// +// It is not necessary to call this if you simply want +// to query the state of the various buttons by index +// number; this is only necessary in order to generate +// ButtonEvents when the buttons change state. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +set_button_map(int index, ButtonHandle button) { + LightMutexHolder holder(_lock); + nassertv(index >= 0); + if (index >= (int)_buttons.size()) { + _buttons.resize(index + 1, ButtonState()); + } + + _buttons[index]._handle = button; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_button_map +// Access: Public +// Description: Returns the ButtonHandle that was previously +// associated with the given index number by +// a call to set_button_map(), or ButtonHandle::none() +// if no button was associated. +//////////////////////////////////////////////////////////////////// +INLINE ButtonHandle InputDevice:: +get_button_map(int index) const { + if (index >= 0 && index < (int)_buttons.size()) { + return _buttons[index]._handle; + } else { + return ButtonHandle::none(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_button_state +// Access: Public +// Description: Returns true if the indicated button (identified by +// its index number) is currently known to be down, or +// false if it is up or unknown. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +get_button_state(int index) const { + if (index >= 0 && index < (int)_buttons.size()) { + return (_buttons[index]._state == S_down); + } else { + return false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::is_button_known +// Access: Public +// Description: Returns true if the state of the indicated button is +// known, or false if we have never heard anything about +// this particular button. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +is_button_known(int index) const { + if (index >= 0 && index < (int)_buttons.size()) { + return _buttons[index]._state != S_unknown; + } else { + return false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_num_controls +// Access: Public +// Description: Returns the number of analog controls known to the +// InputDevice. This number may change as +// more controls are discovered. +//////////////////////////////////////////////////////////////////// +INLINE int InputDevice:: +get_num_controls() const { + return _controls.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_control_map +// Access: Public +// Description: Associates the indicated ControlAxis with the +// control of the indicated index number. Pass +// C_none to turn off any association. +// +// It is not necessary to call this if you simply want +// to query the state of the various controls by index +// number. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +set_control_map(int index, ControlAxis axis) { + LightMutexHolder holder(_lock); + nassertv(index >= 0); + if (index >= (int)_controls.size()) { + _controls.resize(index + 1, AnalogState()); + } + + _controls[index]._axis = axis; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_control_map +// Access: Public +// Description: Returns the ControlAxis that was previously +// associated with the given index number by +// a call to set_control_map(), or C_none +// if no control was associated. +//////////////////////////////////////////////////////////////////// +INLINE InputDevice::ControlAxis InputDevice:: +get_control_map(int index) const { + if (index >= 0 && index < (int)_controls.size()) { + return _controls[index]._axis; + } else { + return C_none; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_control_state +// Access: Public +// Description: Returns the current position of indicated analog +// control (identified by its index number), or 0.0 if +// the control is unknown. The normal range of a single +// control is -1.0 to 1.0. +//////////////////////////////////////////////////////////////////// +INLINE double InputDevice:: +get_control_state(int index) const { + if (index >= 0 && index < (int)_controls.size()) { + return _controls[index]._state; + } else { + return 0.0; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::is_control_known +// Access: Public +// Description: Returns true if the state of the indicated analog +// control is known, or false if we have never heard +// anything about this particular control. +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +is_control_known(int index) const { + if (index >= 0 && index < (int)_controls.size()) { + return _controls[index]._known; + } else { + return false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::enable_pointer_events +// Access: Public +// Description: Enables the generation of mouse-movement events. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +enable_pointer_events() { + LightMutexHolder holder(_lock); + _enable_pointer_events = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::disable_pointer_events +// Access: Public +// Description: Disables the generation of mouse-movement events. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +disable_pointer_events() { + LightMutexHolder holder(_lock); + _enable_pointer_events = false; + _pointer_events.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_connected +// Access: Protected +// Description: Called to indicate that the device has been +// disconnected or connected from its host. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +set_connected(bool connected) { + LightMutexHolder holder(_lock); + _is_connected = connected; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::operator == +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +operator == (const InputDevice &) const { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::operator != +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +operator != (const InputDevice &) const { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::operator < +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE bool InputDevice:: +operator < (const InputDevice &) const { + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::ButtonState::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE InputDevice::ButtonState:: +ButtonState() : + _handle(ButtonHandle::none()), + _state(S_unknown) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::AnalogState::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE InputDevice::AnalogState:: +AnalogState() : + _axis(C_none), + _state(0.0), + _known(false) +{ +} diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx new file mode 100644 index 0000000000..25477ca913 --- /dev/null +++ b/panda/src/device/inputDevice.cxx @@ -0,0 +1,517 @@ +// Filename: inputDevice.cxx +// Created by: drose (24May00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "inputDevice.h" + +TypeHandle InputDevice::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Constructor +// Access: Private +// Description: Defines a new InputDevice for the window. Most +// windows will have exactly one InputDevice: a +// keyboard/mouse pair. Some may also add joystick +// data, or additional mice or something. +// +// This private constructor is only used internally by +// the named constructors, below. +//////////////////////////////////////////////////////////////////// +InputDevice:: +InputDevice(const string &name, DeviceClass dev_class, int flags) : + _name(name), + _flags(flags), + _device_class(dev_class), + _is_connected(true), + _event_sequence(0), + _enable_pointer_events(false), + _battery_level(-1), + _max_battery_level(-1) +{ + _button_events = new ButtonEventList; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +InputDevice:: +InputDevice(const InputDevice ©) { + *this = copy; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Copy Assignment Operator +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void InputDevice:: +operator = (const InputDevice ©) { + LightMutexHolder holder(_lock); + LightMutexHolder holder1(copy._lock); + _name = copy._name; + _flags = copy._flags; + _is_connected = copy._is_connected; + _event_sequence = copy._event_sequence; + _enable_pointer_events = copy._enable_pointer_events; + _pointer_data = copy._pointer_data; + _button_events = copy._button_events; + _pointer_events = copy._pointer_events; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +InputDevice:: +~InputDevice() { +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::poll +// Access: Public, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +poll() { + LightMutexHolder holder(_lock); + do_poll(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_button_event +// Access: Public +// Description: Returns true if this device has a pending button +// event (a mouse button or keyboard button down/up), +// false otherwise. If this returns true, the +// particular event may be extracted via +// get_button_event(). +//////////////////////////////////////////////////////////////////// +bool InputDevice:: +has_button_event() const { + LightMutexHolder holder(_lock); + return !_button_events.is_null() && _button_events->get_num_events() > 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_button_events +// Access: Public +// Description: Returns the list of recently-generated ButtonEvents. +// The list is also cleared. +//////////////////////////////////////////////////////////////////// +PT(ButtonEventList) InputDevice:: +get_button_events() { + LightMutexHolder holder(_lock); + PT(ButtonEventList) result = new ButtonEventList; + swap(_button_events, result); + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::has_pointer_event +// Access: Public +// Description: Returns true if this device has a pending pointer +// event (a mouse movement), or false otherwise. If +// this returns true, the particular event may be +// extracted via get_pointer_event(). +//////////////////////////////////////////////////////////////////// +bool InputDevice:: +has_pointer_event() const { + LightMutexHolder holder(_lock); + return (_pointer_events != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_pointer_events +// Access: Public +// Description: Returns a PointerEventList containing all the recent +// pointer events. Clears the list. +//////////////////////////////////////////////////////////////////// +PT(PointerEventList) InputDevice:: +get_pointer_events() { + LightMutexHolder holder(_lock); + PT(PointerEventList) result = _pointer_events; + _pointer_events.clear(); + return result; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_pointer +// Access: Protected +// Description: Records that a mouse movement has taken place. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_pointer(bool inwin, double x, double y, double time) { + nassertv(_lock.debug_is_locked()); + _pointer_data._in_window = inwin; + _pointer_data._xpos = x; + _pointer_data._ypos = y; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + if (_pointer_events.is_null()) { + _pointer_events = new PointerEventList(); + } + _pointer_events->add_event(_pointer_data._in_window, + _pointer_data._xpos, + _pointer_data._ypos, + seq, time); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_pointer_out_of_window +// Access: Protected +// Description: Records that the mouse pointer has left the window. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_pointer_out_of_window(double time) { + nassertv(_lock.debug_is_locked()); + _pointer_data._in_window = false; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + if (_pointer_events.is_null()) { + _pointer_events = new PointerEventList(); + } + _pointer_events->add_event(_pointer_data._in_window, + _pointer_data._xpos, + _pointer_data._ypos, + seq, time); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_button_state +// Access: Protected +// Description: Sets the state of the indicated button index, where +// true indicates down, and false indicates up. This +// may generate a ButtonEvent if the button has an +// associated ButtonHandle. The caller should ensure +// that acquire() is in effect while this call is made. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_button_state(int index, bool down) { + nassertv(_lock.debug_is_locked()); + nassertv(index >= 0); + if (index >= (int)_buttons.size()) { + _buttons.resize(index + 1, ButtonState()); + } + + _buttons[index]._state = down ? S_down : S_up; + + ButtonHandle handle = _buttons[index]._handle; + if (handle != ButtonHandle::none()) { + _button_events->add_event(ButtonEvent(handle, down ? ButtonEvent::T_down : ButtonEvent::T_up)); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_control_state +// Access: Protected +// Description: Sets the state of the indicated analog index. The +// caller should ensure that acquire() is in effect while +// this call is made. This should be a number in the +// range -1.0 to 1.0, representing the current position +// of the control within its total range of movement. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_control_state(int index, double state) { + nassertv(_lock.debug_is_locked()); + nassertv(index >= 0); + if (index >= (int)_controls.size()) { + _controls.resize(index + 1, AnalogState()); + } + + _controls[index]._state = state; + _controls[index]._known = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_tracker +// Access: Protected +// Description: Records that a tracker movement has taken place. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +set_tracker(const LPoint3 &pos, const LOrientation &orient, double time) { + nassertv(_lock.debug_is_locked()); + + _tracker_data.set_pos(pos); + _tracker_data.set_orient(orient); + _tracker_data.set_time(time); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::output +// Access: Public +// Description: Writes a one-line string describing the device. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +output(ostream &out) const { + LightMutexHolder holder(_lock); + + out << _name << " ("; + + if (!_is_connected) { + out << "dis"; + } + + out << "connected)"; + + if (_device_class != DC_unknown) { + out << ", " << _device_class; + } + + if (_buttons.size() > 0) { + out << ", " << _buttons.size() << " buttons"; + } + + if (_controls.size() > 0) { + out << ", " << _controls.size() << " controls"; + } + + if (_flags & IDF_has_pointer) { + out << ", pointer"; + } + if (_flags & IDF_has_keyboard) { + out << ", keyboard"; + } + if (_flags & IDF_has_tracker) { + out << ", tracker"; + } + if (_flags & IDF_has_vibration) { + out << ", vibration"; + } + if (_flags & IDF_has_battery) { + out << ", battery"; + + if (_battery_level > 0 && _max_battery_level > 0) { + out << " ["; + short i = 0; + for (; i < _battery_level - 1; ++i) { + out << '='; + } + out << '/'; + for (; i < _max_battery_level; ++i) { + out << ' '; + } + out << ']'; + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::output_buttons +// Access: Public +// Description: Writes a one-line string of all of the current button +// states. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +output_buttons(ostream &out) const { + LightMutexHolder holder(_lock); + + bool any_buttons = false; + Buttons::const_iterator bi; + for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { + const ButtonState &state = (*bi); + if (state._state != S_unknown) { + if (any_buttons) { + out << ", "; + } + any_buttons = true; + out << (int)(bi - _buttons.begin()) << "="; + if (state._state == S_up) { + out << "up"; + } else { + out << "down"; + } + } + } + + if (!any_buttons) { + out << "no known buttons"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::write_buttons +// Access: Public +// Description: Writes a multi-line description of the current button +// states. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +write_buttons(ostream &out, int indent_level) const { + bool any_buttons = false; + Buttons::const_iterator bi; + for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { + const ButtonState &state = (*bi); + if (state._state != S_unknown) { + any_buttons = true; + + indent(out, indent_level) + << (int)(bi - _buttons.begin()) << ". "; + + if (state._handle != ButtonHandle::none()) { + out << "(" << state._handle << ") "; + } + + if (state._state == S_up) { + out << "up"; + } else { + out << "down"; + } + out << "\n"; + } + } + + if (!any_buttons) { + indent(out, indent_level) + << "(no known buttons)\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::write_controls +// Access: Public +// Description: Writes a multi-line description of the current analog +// control states. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +write_controls(ostream &out, int indent_level) const { + LightMutexHolder holder(_lock); + + bool any_controls = false; + Controls::const_iterator ai; + for (ai = _controls.begin(); ai != _controls.end(); ++ai) { + const AnalogState &state = (*ai); + if (state._known) { + any_controls = true; + + indent(out, indent_level) + << (int)(ai - _controls.begin()) << ". " << state._state << "\n"; + } + } + + if (!any_controls) { + indent(out, indent_level) + << "(no known analog controls)\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::do_poll +// Access: Protected, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +do_poll() { +} + +ostream & +operator << (ostream &out, InputDevice::DeviceClass dc) { + switch (dc) { + case InputDevice::DC_unknown: + out << "unknown"; + break; + + case InputDevice::DC_virtual: + out << "virtual"; + break; + + case InputDevice::DC_keyboard: + out << "keyboard"; + break; + + case InputDevice::DC_mouse: + out << "mouse"; + break; + + case InputDevice::DC_touch: + out << "touch"; + break; + + case InputDevice::DC_gamepad: + out << "gamepad"; + break; + + case InputDevice::DC_flight_stick: + out << "flight_stick"; + break; + + case InputDevice::DC_steering_wheel: + out << "steering_wheel"; + break; + } + return out; +} + +ostream & +operator << (ostream &out, InputDevice::ControlAxis axis) { + switch (axis) { + case InputDevice::C_none: + out << "none"; + break; + + case InputDevice::C_left_x: + out << "left_x"; + break; + + case InputDevice::C_left_y: + out << "left_y"; + break; + + case InputDevice::C_left_trigger: + out << "left_trigger"; + break; + + case InputDevice::C_right_x: + out << "right_x"; + break; + + case InputDevice::C_right_y: + out << "right_y"; + break; + + case InputDevice::C_right_trigger: + out << "right_trigger"; + break; + + case InputDevice::C_x: + out << "x"; + break; + + case InputDevice::C_y: + out << "y"; + break; + + case InputDevice::C_trigger: + out << "trigger"; + break; + + case InputDevice::C_throttle: + out << "throttle"; + break; + } + + return out; +} diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h new file mode 100644 index 0000000000..407e6cdb77 --- /dev/null +++ b/panda/src/device/inputDevice.h @@ -0,0 +1,279 @@ +// Filename: inputDevice.h +// Created by: drose (24May00) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef INPUTDEVICE_H +#define INPUTDEVICE_H + +#include "pandabase.h" + +#include "buttonEvent.h" +#include "buttonEventList.h" +#include "pointerEvent.h" +#include "pointerEventList.h" +#include "mouseData.h" +#include "trackerData.h" +#include "clockObject.h" + +#include "pdeque.h" +#include "pvector.h" +#include "lightMutex.h" +#include "lightMutexHolder.h" + +typedef MouseData PointerData; + +//////////////////////////////////////////////////////////////////// +// Class : InputDevice +// Description : This is a structure representing a single input +// device. Input devices may have zero or more +// buttons, pointers, or controls associated with them, +// and optionally a tracker. +// +// These devices are brought under a common interface +// because there is such a large range of devices out +// there that may support any number of these types of +// controls, we couldn't even begin to cover them with +// type-specific subclasses. +// +// Use the various has_() and get_num_() methods to +// determine information about the device capabilities. +// For instance, has_keyboard() will give an indication +// that you can receive keystroke events from this +// device, and get_num_buttons() will tell you that +// the device may send button events. +// +// There is the DeviceType enumeration, however, which +// will (if known) contain identification about the +// general category of devices this fits in, such as +// keyboard, mouse, gamepad, or flight stick. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE InputDevice : public TypedReferenceCount { +PUBLISHED: + // This enum contains information that can be used to identify the + // type of input device. + enum DeviceClass { + DC_unknown, + + // This means that the device doesn't correspond to a physical + // device, but rather to a dynamic source of input events. + DC_virtual, + + // A physical, alphabetical keyboard. + DC_keyboard, + + DC_mouse, + DC_touch, + + // A gamepad with action buttons, a D-pad, and thumbsticks. + DC_gamepad, + + DC_flight_stick, + DC_steering_wheel, + }; + +protected: + InputDevice(const string &name, DeviceClass dev_class, int flags); + +public: + InputDevice(); + InputDevice(const InputDevice ©); + void operator = (const InputDevice ©); + ~InputDevice(); + +PUBLISHED: + enum ControlAxis { + C_none, + + // Gamepad + C_left_x, + C_left_y, + C_left_trigger, + C_right_x, + C_right_y, + C_right_trigger, + + // Flight stick + C_x, + C_y, + C_trigger, + C_throttle, + }; + + INLINE string get_name() const; + INLINE bool is_connected() const; + INLINE DeviceClass get_device_class() const; + + // The human-readable name of this input device. + MAKE_PROPERTY(name, get_name); + + // This is false if we know that the device is not currently connected. + // May report false positives if we can't know this with certainty. + MAKE_PROPERTY(connected, is_connected); + + // This contains an identification of the general type of device. If + // this could not be determined, it is set to DC_unknown. + MAKE_PROPERTY(device_class, get_device_class); + + INLINE bool has_pointer() const; + INLINE bool has_keyboard() const; + INLINE bool has_tracker() const; + INLINE bool has_battery() const; + + INLINE PointerData get_pointer() const; + INLINE TrackerData get_tracker() const; + + INLINE short get_battery_level() const; + INLINE short get_max_battery_level() const; + + INLINE int get_num_buttons() const; + INLINE void set_button_map(int index, ButtonHandle button); + INLINE ButtonHandle get_button_map(int index) const; + INLINE bool get_button_state(int index) const; + INLINE bool is_button_known(int index) const; + + INLINE int get_num_controls() const; + INLINE void set_control_map(int index, ControlAxis axis); + INLINE ControlAxis get_control_map(int index) const; + INLINE double get_control_state(int index) const; + INLINE bool is_control_known(int index) const; + + INLINE void enable_pointer_events(); + INLINE void disable_pointer_events(); + + void poll(); + + bool has_button_event() const; + PT(ButtonEventList) get_button_events(); + bool has_pointer_event() const; + PT(PointerEventList) get_pointer_events(); + + virtual void output(ostream &out) const; + +protected: + void set_pointer(bool inwin, double x, double y, double time); + void set_pointer_out_of_window(double time); + void set_button_state(int index, bool down); + void set_control_state(int index, double state); + void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); + + virtual void do_poll(); + +public: + INLINE void set_connected(bool connected); + + // We need these methods to make VC++ happy when we try to + // instantiate a pvector. They don't do + // anything useful. + INLINE bool operator == (const InputDevice &other) const; + INLINE bool operator != (const InputDevice &other) const; + INLINE bool operator < (const InputDevice &other) const; + + void output_buttons(ostream &out) const; + void write_buttons(ostream &out, int indent_level) const; + void write_controls(ostream &out, int indent_level) const; + +protected: + enum InputDeviceFlags { + // The device provides absolute screen coordinates. + IDF_has_pointer = 0x01, + + // The device has an interface for providing text input. + IDF_has_keyboard = 0x02, + + // The device has a motion tracker, such as an HMD. + IDF_has_tracker = 0x04, + + // The device can produce force feedback. + IDF_has_vibration = 0x08, + + // The device provides information about battery life. + IDF_has_battery = 0x10, + }; + +protected: + typedef pdeque ButtonEvents; + + LightMutex _lock; + + string _name; + DeviceClass _device_class; + int _flags; + int _event_sequence; + bool _is_connected; + bool _enable_pointer_events; + PointerData _pointer_data; + PT(ButtonEventList) _button_events; + PT(PointerEventList) _pointer_events; + +public: + enum State { + S_unknown, + S_up, + S_down + }; + + class ButtonState { + public: + INLINE ButtonState(); + + ButtonHandle _handle; + State _state; + }; + typedef pvector Buttons; + Buttons _buttons; + + class AnalogState { + public: + INLINE AnalogState(); + + ControlAxis _axis; + double _state; + bool _known; + }; + typedef pvector Controls; + Controls _controls; + + short _battery_level; + short _max_battery_level; + + TrackerData _tracker_data; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + TypedReferenceCount::init_type(); + register_type(_type_handle, "InputDevice", + TypedReferenceCount::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +INLINE ostream &operator << (ostream &out, const InputDevice &device) { + device.output(out); + return out; +} + +ostream &operator << (ostream &out, InputDevice::DeviceClass dc); +ostream &operator << (ostream &out, InputDevice::ControlAxis axis); + +#include "inputDevice.I" + +#endif diff --git a/panda/src/device/inputDeviceManager.I b/panda/src/device/inputDeviceManager.I new file mode 100644 index 0000000000..21f687f621 --- /dev/null +++ b/panda/src/device/inputDeviceManager.I @@ -0,0 +1,27 @@ +// Filename: inputDeviceManager.I +// Created by: rdb (09Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::get_global_ptr +// Access: Published, Static +// Description: Returns the singleton instance. +//////////////////////////////////////////////////////////////////// +INLINE InputDeviceManager *InputDeviceManager:: +get_global_ptr() { + if (_global_ptr == (InputDeviceManager *)NULL) { + _global_ptr = new InputDeviceManager; + } + return _global_ptr; +} diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx new file mode 100644 index 0000000000..d67e21208f --- /dev/null +++ b/panda/src/device/inputDeviceManager.cxx @@ -0,0 +1,240 @@ +// Filename: inputDeviceManager.cxx +// Created by: rdb (09Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "inputDeviceManager.h" +#include "linuxJoystickDevice.h" +#include "throw_event.h" + +#ifdef PHAVE_LINUX_INPUT_H +#include +#include +#include +#include +#endif + +InputDeviceManager *InputDeviceManager::_global_ptr = NULL; + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::Constructor +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +InputDeviceManager:: +InputDeviceManager() : _inotify_fd(-1) { +#ifdef PHAVE_LINUX_INPUT_H + // Scan /dev/input for a list of input devices. + DIR *dir = opendir("/dev/input"); + if (dir) { + dirent *entry = readdir(dir); + while (entry != NULL) { + if (entry->d_type == DT_CHR) { + string name(entry->d_name); + if (!consider_add_linux_device(name)) { + // We can't access it. That's pretty normal for most devices. + if (device_cat.is_debug()) { + device_cat.debug() + << "Ignoring input device /dev/input/" << name << ": " + << strerror(errno) << "\n"; + } + errno = 0; + } + } + entry = readdir(dir); + } + closedir(dir); + } else { + device_cat.error() + << "Error opening directory /dev/input: " << strerror(errno) << "\n"; + return; + } + + // Use inotify to watch /dev/input for hotplugging of devices. + _inotify_fd = inotify_init1(O_NONBLOCK); + + if (_inotify_fd < 0) { + device_cat.error() + << "Error initializing inotify: " << strerror(errno) << "\n"; + + } else if (inotify_add_watch(_inotify_fd, "/dev/input", IN_CREATE | IN_ATTRIB | IN_DELETE) < 0) { + device_cat.error() + << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; + } +#endif +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::Destructor +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +InputDeviceManager:: +~InputDeviceManager() { +#ifdef PHAVE_LINUX_INPUT_H + if (_inotify_fd >= 0) { + close(_inotify_fd); + _inotify_fd = -1; + } +#endif +} + +#ifdef PHAVE_LINUX_INPUT_H +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::consider_add_linux_device +// Access: Private +// Description: Checks whether the given device is accessible, and +// if so, adds it. Returns false on error. +//////////////////////////////////////////////////////////////////// +bool InputDeviceManager:: +consider_add_linux_device(const string &name) { + // Get the full path name first. + string path = "/dev/input/"; + path += name; + + if (access(path.c_str(), R_OK) < 0) { + return false; + } + + if (_devices_by_path.count(name)) { + // We already have this device. + return true; + } + + // Check if it's a joystick or game controller device. + if (name.size() > 2 && name[0] == 'j' && name[1] == 's' && isdigit(name[2])) { + PT(InputDevice) device = new LinuxJoystickDevice(path); + if (device_cat.is_info()) { + device_cat.info() + << "Discovered input device " << *device << "\n"; + } + + _devices_by_path[name] = device; + _all_devices.push_back(MOVE(device)); + return true; + } + + return true; +} +#endif + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::add_device +// Access: Public +// Description: Called when a new device has been discovered. +//////////////////////////////////////////////////////////////////// +void InputDeviceManager:: +add_device(InputDevice *device) { + { + LightMutexHolder holder(_lock); + _all_devices.push_back(device); + } + throw_event("device-added", device); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::remove_device +// Access: Public +// Description: Called when a device has been removed. +//////////////////////////////////////////////////////////////////// +void InputDeviceManager:: +remove_device(InputDevice *device) { + { + LightMutexHolder holder(_lock); + + InputDevices::iterator it; + it = std::find(_all_devices.begin(), _all_devices.end(), device); + nassertv_always(it != _all_devices.end()); + + _all_devices.erase(it); + } + + throw_event("device-removed", device); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::poll +// Access: Public +// Description: Polls the system to see if there are any new devices. +//////////////////////////////////////////////////////////////////// +void InputDeviceManager:: +poll() { +#ifdef PHAVE_LINUX_INPUT_H + // We use inotify to tell us whether a device was added, removed, + // or has changed permissions to allow us to access it. + unsigned int avail; + ioctl(_inotify_fd, FIONREAD, &avail); + if (avail == 0) { + return; + } + + char buffer[avail]; + int n_read = read(_inotify_fd, buffer, avail); + if (n_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available for now. + + } else { + device_cat.error() << "read: " << strerror(errno) << "\n"; + } + return; + } + + LightMutexHolder holder(_lock); + + // Iterate over the events in the buffer. + char *ptr = buffer; + char *end = buffer + avail; + while (ptr < end) { + inotify_event *event = (inotify_event *)ptr; + + string name(event->name); + + if (event->mask & IN_DELETE) { + // The device was deleted. If we have it, remove it. + DevicesByPath::iterator it = _devices_by_path.find(name); + if (it != _devices_by_path.end()) { + PT(InputDevice) device = it->second; + device->set_connected(false); + + InputDevices::iterator it2; + _devices_by_path.erase(it); + it2 = std::find(_all_devices.begin(), _all_devices.end(), device); + _all_devices.erase(it2); + + if (device_cat.is_info()) { + device_cat.info() + << "Removed input device " << *device << "\n"; + } + throw_event("device-removed", device.p()); + } + + } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { + // The device was created, or it was chmodded to be accessible. + DevicesByPath::iterator it = _devices_by_path.find(name); + if (it == _devices_by_path.end()) { + // We don't know about this device yet. + if (!consider_add_linux_device(name) && (event->mask & IN_CREATE) != 0) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Ignoring input device /dev/input/" << name << ": " + << strerror(errno) << "\n"; + } + } + errno = 0; + } + } + + ptr += sizeof(inotify_event) + event->len; + } +#endif +} diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h new file mode 100644 index 0000000000..3c05cf96fd --- /dev/null +++ b/panda/src/device/inputDeviceManager.h @@ -0,0 +1,64 @@ +// Filename: inputDeviceManager.h +// Created by: rdb (09Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef INPUTDEVICEMANAGER_H +#define INPUTDEVICEMANAGER_H + +#include "pandabase.h" +#include "lightMutex.h" +#include "inputDevice.h" + +//////////////////////////////////////////////////////////////////// +// Class : InputDeviceManager +// Description : This class keeps track of all the devices on a +// system, and sends out events when a device has been +// hot-plugged. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE InputDeviceManager { +private: + InputDeviceManager(); + ~InputDeviceManager(); + +#ifdef PHAVE_LINUX_INPUT_H + bool consider_add_linux_device(const string &name); +#endif + +public: + void add_device(InputDevice *device); + void remove_device(InputDevice *device); + +PUBLISHED: + void poll(); + + INLINE static InputDeviceManager *get_global_ptr(); + +private: + LightMutex _lock; + +#ifdef PHAVE_LINUX_INPUT_H + int _inotify_fd; + + typedef pmap DevicesByPath; + DevicesByPath _devices_by_path; +#endif + + typedef pvector InputDevices; + InputDevices _all_devices; + + static InputDeviceManager *_global_ptr; +}; + +#include "inputDeviceManager.I" + +#endif diff --git a/panda/src/device/linuxJoystickDevice.I b/panda/src/device/linuxJoystickDevice.I new file mode 100644 index 0000000000..b1150e12a5 --- /dev/null +++ b/panda/src/device/linuxJoystickDevice.I @@ -0,0 +1,14 @@ +// Filename: linuxJoystickDevice.I +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx new file mode 100644 index 0000000000..b3566554e6 --- /dev/null +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -0,0 +1,313 @@ +// Filename: linuxJoystickDevice.cxx +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "linuxJoystickDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +#include "gamepadButton.h" + +#include +#include + +TypeHandle LinuxJoystickDevice::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::Constructor +// Access: Private +// Description: Creates a new device using the Linux joystick +// device using the given device filename. +//////////////////////////////////////////////////////////////////// +LinuxJoystickDevice:: +LinuxJoystickDevice(const string &device) : + _fd(-1), + _device(device) +{ + LightMutexHolder holder(_lock); + if (!open_device()) { + device_cat.error() + << "Could not open joystick device " << _device + << ": " << strerror(errno) << "\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +LinuxJoystickDevice:: +~LinuxJoystickDevice() { + if (_fd != -1) { + close(_fd); + _fd = -1; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::do_poll +// Access: Private, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void LinuxJoystickDevice:: +do_poll() { + if (_fd != -1) { + while (process_events()) {} + } else { + open_device(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::open_device +// Access: Private +// Description: Opens or reopens the joystick device, and reads out +// the button and axis mappings. +// Assumes the lock has been held. +//////////////////////////////////////////////////////////////////// +bool LinuxJoystickDevice:: +open_device() { + nassertr(_lock.debug_is_locked(), false); + + _fd = open(_device.c_str(), O_RDONLY | O_NONBLOCK); + + if (_fd == -1) { + _is_connected = false; + return false; + } + + // Read the name from the device. + char name[128]; + name[0] = 0; + ioctl(_fd, JSIOCGNAME(sizeof(name)), name); + _name = name; + + // Get the number of axes. + PN_uint8 num_axes = 0, num_buttons = 0; + ioctl(_fd, JSIOCGAXES, &num_axes); + ioctl(_fd, JSIOCGBUTTONS, &num_buttons); + + _buttons.resize(num_buttons); + _controls.resize(num_axes); + + if (num_buttons > 0) { + PN_uint16 btnmap[512]; + ioctl(_fd, JSIOCGBTNMAP, btnmap); + + for (char i = 0; i < num_buttons; ++i) { + ButtonHandle handle = ButtonHandle::none(); + switch (btnmap[i]) { + case BTN_A: + handle = GamepadButton::action_a(); + _device_class = DC_gamepad; + break; + + case BTN_B: + handle = GamepadButton::action_b(); + break; + + case BTN_C: + handle = GamepadButton::action_c(); + break; + + case BTN_X: + handle = GamepadButton::action_x(); + break; + + case BTN_Y: + handle = GamepadButton::action_y(); + break; + + case BTN_Z: + handle = GamepadButton::action_z(); + break; + + case BTN_TL: + handle = GamepadButton::lshoulder(); + break; + + case BTN_TR: + handle = GamepadButton::rshoulder(); + break; + + case BTN_TL2: + handle = GamepadButton::ltrigger(); + break; + + case BTN_TR2: + handle = GamepadButton::rtrigger(); + break; + + case BTN_SELECT: + handle = GamepadButton::back(); + break; + + case BTN_START: + handle = GamepadButton::start(); + break; + + case BTN_MODE: + handle = GamepadButton::guide(); + break; + + case BTN_THUMBL: + handle = GamepadButton::lstick(); + break; + + case BTN_THUMBR: + handle = GamepadButton::rstick(); + break; + + case BTN_TRIGGER_HAPPY1: + handle = GamepadButton::dpad_left(); + break; + + case BTN_TRIGGER_HAPPY2: + handle = GamepadButton::dpad_right(); + break; + + case BTN_TRIGGER_HAPPY3: + handle = GamepadButton::dpad_up(); + break; + + case BTN_TRIGGER_HAPPY4: + handle = GamepadButton::dpad_down(); + break; + + default: + handle = ButtonHandle::none(); + break; + } + _buttons[i]._handle = handle; + } + } + + if (num_axes > 0) { + PN_uint8 axmap[512]; + ioctl(_fd, JSIOCGAXMAP, axmap); + + for (char i = 0; i < num_axes; ++i) { + ControlAxis axis = C_none; + + switch (axmap[i]) { + case ABS_X: + if (_device_class == DC_gamepad) { + axis = C_left_x; + } else { + axis = C_x; + } + break; + + case ABS_Y: + if (_device_class == DC_gamepad) { + axis = C_left_y; + } else { + axis = C_y; + } + break; + + case ABS_Z: + if (_device_class == DC_gamepad) { + axis = C_left_trigger; + } else { + axis = C_trigger; + } + break; + + case ABS_RX: + axis = C_right_x; + break; + + case ABS_RY: + axis = C_right_y; + break; + + case ABS_RZ: + axis = C_right_trigger; + break; + + default: + axis = C_none; + break; + } + _controls[i]._axis = axis; + } + } + + // Read the init events. + _is_connected = true; + while (process_events()) {}; + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::process_events +// Access: Private +// Description: Reads a number of events from the joystick. +//////////////////////////////////////////////////////////////////// +bool LinuxJoystickDevice:: +process_events() { + // Read 8 events at a time. + struct js_event events[8]; + + int n_read = read(_fd, events, sizeof(events)); + if (n_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available for now. + + } else if (errno == ENODEV) { + // The device ceased to exist, so we better close it. + close(_fd); + _fd = -1; + _is_connected = false; + errno = 0; + + } else { + device_cat.error() << "read: " << strerror(errno) << "\n"; + } + return false; + } + + if (n_read == 0) { + return false; + } + + n_read /= sizeof(struct js_event); + + for (int i = 0; i < n_read; ++i) { + int index = events[i].number; + + if (events[i].type & JS_EVENT_BUTTON) { + set_button_state(index, (events[i].value != 0)); + + } else if (events[i].type & JS_EVENT_AXIS) { + ControlAxis axis = _controls[index]._axis; + + if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { + // We'd like to use 0.0 to indicate the resting position. + set_control_state(index, (events[i].value + 32767) / 65534.0); + } else { + set_control_state(index, events[i].value / 32767.0); + } + } + } + + return true; +} + +#endif diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h new file mode 100644 index 0000000000..d71ad45c17 --- /dev/null +++ b/panda/src/device/linuxJoystickDevice.h @@ -0,0 +1,60 @@ +// Filename: linuxJoystickDevice.h +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef LINUXJOYSTICKDEVICE_H +#define LINUXJOYSTICKDEVICE_H + +#include "inputDevice.h" + +#ifdef PHAVE_LINUX_INPUT_H + +//////////////////////////////////////////////////////////////////// +// Class : LinuxJoystickDevice +// Description : This is a type of device that uses the Linux +// /dev/input/js# API to read data from a game controller. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice { +PUBLISHED: + LinuxJoystickDevice(const string &device); + virtual ~LinuxJoystickDevice(); + +private: + virtual void do_poll(); + + bool open_device(); + bool process_events(); + +private: + int _fd; + string _device; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + InputDevice::init_type(); + register_type(_type_handle, "LinuxJoystickDevice", + InputDevice::get_class_type()); + } + +private: + static TypeHandle _type_handle; +}; + +#include "linuxJoystickDevice.I" + +#endif // PHAVE_LINUX_INPUT_H + +#endif // LINUXJOYSTICKDEVICE_H diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index b48fae48ec..80fa832775 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -5,4 +5,7 @@ #include "clientDevice.cxx" #include "clientDialDevice.cxx" #include "clientTrackerDevice.cxx" - +#include "evdevInputDevice.cxx" +#include "inputDevice.cxx" +#include "inputDeviceManager.cxx" +#include "linuxJoystickDevice.cxx" diff --git a/panda/src/device/p3device_composite2.cxx b/panda/src/device/p3device_composite2.cxx index 9457fca14c..294e5d69ae 100644 --- a/panda/src/device/p3device_composite2.cxx +++ b/panda/src/device/p3device_composite2.cxx @@ -3,7 +3,6 @@ #include "analogNode.cxx" #include "buttonNode.cxx" #include "dialNode.cxx" -#include "mouseAndKeyboard.cxx" #include "trackerData.cxx" #include "trackerNode.cxx" #include "virtualMouse.cxx" diff --git a/panda/src/device/trackerNode.I b/panda/src/device/trackerNode.I index 5f872c7ea3..5aa067e730 100644 --- a/panda/src/device/trackerNode.I +++ b/panda/src/device/trackerNode.I @@ -20,10 +20,9 @@ //////////////////////////////////////////////////////////////////// INLINE bool TrackerNode:: is_valid() const { - return (_tracker != (ClientTrackerDevice *)NULL) && _tracker->is_connected(); + return (!_tracker.is_null()/* && _tracker->is_connected()*/); } - //////////////////////////////////////////////////////////////////// // Function: TrackerNode::get_pos // Access: Public diff --git a/panda/src/device/trackerNode.cxx b/panda/src/device/trackerNode.cxx index edc20ca07d..095fbfc141 100644 --- a/panda/src/device/trackerNode.cxx +++ b/panda/src/device/trackerNode.cxx @@ -51,7 +51,7 @@ TrackerNode(ClientBase *client, const string &device_name) : return; } - _tracker = DCAST(ClientTrackerDevice, device); + _tracker = device; } //////////////////////////////////////////////////////////////////// @@ -60,18 +60,19 @@ TrackerNode(ClientBase *client, const string &device_name) : // Description: //////////////////////////////////////////////////////////////////// TrackerNode:: -TrackerNode(ClientTrackerDevice *device) : - DataNode(device->get_device_name()), +TrackerNode(InputDevice *device) : + DataNode(device->get_name()), _tracker(device) { _transform_output = define_output("transform", TransformState::get_class_type()); _transform = TransformState::make_identity(); - nassertv(device != (ClientTrackerDevice *)NULL); - ClientBase *client = device->get_client(); - nassertv(client != (ClientBase *)NULL); - set_tracker_coordinate_system(client->get_coordinate_system()); + nassertv(device != (InputDevice *)NULL); + nassertv(device->has_tracker()); + + //TODO: get coordinate system from tracker object? + set_tracker_coordinate_system(CS_default); set_graph_coordinate_system(CS_default); } @@ -105,9 +106,7 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { if (is_valid()) { _tracker->poll(); - _tracker->acquire(); - _data = _tracker->get_data(); - _tracker->unlock(); + _data = _tracker->get_tracker(); _data.get_orient().extract_to_matrix(_mat); if (_tracker_cs != _graph_cs) { diff --git a/panda/src/device/trackerNode.h b/panda/src/device/trackerNode.h index 8010c9da7b..9cef1c2d7f 100644 --- a/panda/src/device/trackerNode.h +++ b/panda/src/device/trackerNode.h @@ -19,7 +19,7 @@ #include "clientBase.h" #include "trackerData.h" -#include "clientTrackerDevice.h" +#include "inputDevice.h" #include "dataNode.h" #include "luse.h" #include "linmath_events.h" @@ -27,16 +27,16 @@ //////////////////////////////////////////////////////////////////// // Class : TrackerNode -// Description : This is the primary interface to a Tracker object -// associated with a ClientBase. It reads the position -// and orientation information from the tracker and -// makes it available as a transformation on the data -// graph. +// Description : This class reads the position and orientation +// information from a tracker device and makes it +// available as a transformation on the data graph. +// It is also the primary interface to a Tracker object +// associated with a ClientBase. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_DEVICE TrackerNode : public DataNode { PUBLISHED: TrackerNode(ClientBase *client, const string &device_name); - TrackerNode(ClientTrackerDevice *device); + TrackerNode(InputDevice *device); virtual ~TrackerNode(); INLINE bool is_valid() const; @@ -66,7 +66,7 @@ private: CPT(TransformState) _transform; private: - PT(ClientTrackerDevice) _tracker; + PT(InputDevice) _tracker; TrackerData _data; LMatrix4 _mat; CoordinateSystem _tracker_cs, _graph_cs; diff --git a/panda/src/display/callbackGraphicsWindow.cxx b/panda/src/display/callbackGraphicsWindow.cxx index 6de33cc9d6..af1d4169e7 100644 --- a/panda/src/display/callbackGraphicsWindow.cxx +++ b/panda/src/display/callbackGraphicsWindow.cxx @@ -54,22 +54,6 @@ CallbackGraphicsWindow:: ~CallbackGraphicsWindow() { } - -//////////////////////////////////////////////////////////////////// -// Function: CallbackGraphicsWindow::get_input_device -// Access: Published -// Description: Returns a writable reference to the nth input device -// (mouse). This is intended to be used for the window -// implementation to record mouse and keyboard input -// information for the Panda system. -//////////////////////////////////////////////////////////////////// -GraphicsWindowInputDevice &CallbackGraphicsWindow:: -get_input_device(int device) { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), _input_devices[0]); - return _input_devices[device]; -} - //////////////////////////////////////////////////////////////////// // Function: CallbackGraphicsWindow::create_input_device // Access: Published @@ -79,9 +63,7 @@ get_input_device(int device) { //////////////////////////////////////////////////////////////////// int CallbackGraphicsWindow:: create_input_device(const string &name) { - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard(this, name); - return add_input_device(device); + return add_input_device(GraphicsWindowInputDevice::pointer_and_keyboard(this, name)); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/display/callbackGraphicsWindow.h b/panda/src/display/callbackGraphicsWindow.h index c34d324126..e1633959db 100644 --- a/panda/src/display/callbackGraphicsWindow.h +++ b/panda/src/display/callbackGraphicsWindow.h @@ -182,7 +182,6 @@ PUBLISHED: INLINE void clear_render_callback(); INLINE CallbackObject *get_render_callback() const; - GraphicsWindowInputDevice &get_input_device(int device); int create_input_device(const string &name); public: diff --git a/panda/src/display/config_display.cxx b/panda/src/display/config_display.cxx index a62e6a4fb0..f98508894b 100644 --- a/panda/src/display/config_display.cxx +++ b/panda/src/display/config_display.cxx @@ -22,9 +22,11 @@ #include "graphicsPipe.h" #include "graphicsOutput.h" #include "graphicsBuffer.h" -#include "graphicsWindow.h" #include "graphicsDevice.h" +#include "graphicsWindow.h" +#include "graphicsWindowInputDevice.h" #include "graphicsWindowProcCallbackData.h" +#include "mouseAndKeyboard.h" #include "nativeWindowHandle.h" #include "parasiteBuffer.h" #include "pandaSystem.h" @@ -501,7 +503,9 @@ init_libdisplay() { GraphicsPipe::init_type(); GraphicsStateGuardian::init_type(); GraphicsWindow::init_type(); + GraphicsWindowInputDevice::init_type(); GraphicsWindowProcCallbackData::init_type(); + MouseAndKeyboard::init_type(); NativeWindowHandle::init_type(); ParasiteBuffer::init_type(); StandardMunger::init_type(); diff --git a/panda/src/display/graphicsWindow.cxx b/panda/src/display/graphicsWindow.cxx index 6166f9cbac..b6d4976181 100644 --- a/panda/src/display/graphicsWindow.cxx +++ b/panda/src/display/graphicsWindow.cxx @@ -288,6 +288,20 @@ get_num_input_devices() const { return result; } +//////////////////////////////////////////////////////////////////// +// Function: GraphicsWindow::get_input_device +// Access: Published +// Description: Returns the nth input device associated with the +// window. Typically, a window will have exactly one +// input device: the keyboard/mouse pair. +//////////////////////////////////////////////////////////////////// +InputDevice *GraphicsWindow:: +get_input_device(int device) const { + LightMutexHolder holder(_input_lock); + nassertr(device >= 0 && device < (int)_input_devices.size(), NULL); + return _input_devices[device]; +} + //////////////////////////////////////////////////////////////////// // Function: GraphicsWindow::get_input_device_name // Access: Published @@ -299,7 +313,7 @@ get_input_device_name(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), ""); - result = _input_devices[device].get_name(); + result = _input_devices[device]->get_name(); } return result; } @@ -317,7 +331,7 @@ has_pointer(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), false); - result = _input_devices[device].has_pointer(); + result = _input_devices[device]->has_pointer(); } return result; } @@ -334,7 +348,7 @@ has_keyboard(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), false); - result = _input_devices[device].has_keyboard(); + result = _input_devices[device]->has_keyboard(); } return result; } @@ -359,7 +373,7 @@ void GraphicsWindow:: enable_pointer_events(int device) { LightMutexHolder holder(_input_lock); nassertv(device >= 0 && device < (int)_input_devices.size()); - _input_devices[device].enable_pointer_events(); + _input_devices[device]->enable_pointer_events(); } //////////////////////////////////////////////////////////////////// @@ -371,7 +385,7 @@ void GraphicsWindow:: disable_pointer_events(int device) { LightMutexHolder holder(_input_lock); nassertv(device >= 0 && device < (int)_input_devices.size()); - _input_devices[device].disable_pointer_events(); + _input_devices[device]->disable_pointer_events(); } //////////////////////////////////////////////////////////////////// @@ -379,11 +393,11 @@ disable_pointer_events(int device) { // Access: Published // Description: See GraphicsWindowInputDevice::enable_pointer_mode //////////////////////////////////////////////////////////////////// -void GraphicsWindow:: +/*void GraphicsWindow:: enable_pointer_mode(int device, double speed) { LightMutexHolder holder(_input_lock); nassertv(device >= 0 && device < (int)_input_devices.size()); - _input_devices[device].enable_pointer_mode(speed); + _input_devices[device]->enable_pointer_mode(speed); } //////////////////////////////////////////////////////////////////// @@ -395,8 +409,8 @@ void GraphicsWindow:: disable_pointer_mode(int device) { LightMutexHolder holder(_input_lock); nassertv(device >= 0 && device < (int)_input_devices.size()); - _input_devices[device].disable_pointer_mode(); -} + _input_devices[device]->disable_pointer_mode(); +}*/ //////////////////////////////////////////////////////////////////// // Function: GraphicsWindow::get_pointer @@ -412,7 +426,7 @@ get_pointer(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData()); - result = _input_devices[device].get_pointer(); + result = _input_devices[device]->get_pointer(); } return result; } @@ -443,81 +457,6 @@ close_ime() { return; } -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindow::has_button_event -// Access: Public -// Description: Returns true if the indicated device has a pending -// button event (a mouse button or keyboard button -// down/up), false otherwise. If this returns true, the -// particular event may be extracted via -// get_button_event(). -//////////////////////////////////////////////////////////////////// -bool GraphicsWindow:: -has_button_event(int device) const { - bool result; - { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), false); - result = _input_devices[device].has_button_event(); - } - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindow::get_button_event -// Access: Public -// Description: Assuming a previous call to has_button_event() -// returned true, this returns the pending button event. -//////////////////////////////////////////////////////////////////// -ButtonEvent GraphicsWindow:: -get_button_event(int device) { - ButtonEvent result; - { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), ButtonEvent()); - nassertr(_input_devices[device].has_button_event(), ButtonEvent()); - result = _input_devices[device].get_button_event(); - } - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindow::has_pointer_event -// Access: Public -// Description: Returns true if the indicated device has a pending -// pointer event (a mouse movement). If this returns -// true, the particular event may be extracted via -// get_pointer_events(). -//////////////////////////////////////////////////////////////////// -bool GraphicsWindow:: -has_pointer_event(int device) const { - bool result; - { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), false); - result = _input_devices[device].has_pointer_event(); - } - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindow::get_pointer_events -// Access: Public -// Description: Assuming a previous call to has_pointer_event() -// returned true, this returns the pending pointer event list. -//////////////////////////////////////////////////////////////////// -PT(PointerEventList) GraphicsWindow:: -get_pointer_events(int device) { - PT(PointerEventList) result; - { - LightMutexHolder holder(_input_lock); - nassertr(device >= 0 && device < (int)_input_devices.size(), NULL); - nassertr(_input_devices[device].has_pointer_event(), NULL); - result = _input_devices[device].get_pointer_events(); - } - return result; -} - //////////////////////////////////////////////////////////////////// // Function: GraphicsWindow::verify_window_sizes // Access: Public, Virtual @@ -860,11 +799,15 @@ system_changed_size(int x_size, int y_size) { // Returns the index of the new device. //////////////////////////////////////////////////////////////////// int GraphicsWindow:: -add_input_device(const GraphicsWindowInputDevice &device) { +add_input_device(InputDevice *device) { LightMutexHolder holder(_input_lock); int index = (int)_input_devices.size(); _input_devices.push_back(device); - _input_devices.back().set_device_index(index); + + if (device->is_of_type(GraphicsWindowInputDevice::get_class_type())) { + ((GraphicsWindowInputDevice *)device)->set_device_index(index); + } + return index; } diff --git a/panda/src/display/graphicsWindow.h b/panda/src/display/graphicsWindow.h index e7ebab8c80..dbeae1e1da 100644 --- a/panda/src/display/graphicsWindow.h +++ b/panda/src/display/graphicsWindow.h @@ -82,10 +82,12 @@ PUBLISHED: INLINE WindowHandle *get_window_handle() const; MAKE_PROPERTY(window_handle, get_window_handle); - + // Mouse and keyboard routines int get_num_input_devices() const; + InputDevice *get_input_device(int i) const; string get_input_device_name(int device) const; + MAKE_SEQ(get_input_devices, get_num_input_devices, get_input_device); MAKE_SEQ(get_input_device_names, get_num_input_devices, get_input_device_name); bool has_pointer(int device) const; bool has_keyboard(int device) const; @@ -93,20 +95,14 @@ PUBLISHED: void enable_pointer_events(int device); void disable_pointer_events(int device); - void enable_pointer_mode(int device, double speed); - void disable_pointer_mode(int device); + /*void enable_pointer_mode(int device, double speed); + void disable_pointer_mode(int device);*/ MouseData get_pointer(int device) const; virtual bool move_pointer(int device, int x, int y); virtual void close_ime(); public: - // No need to publish these. - bool has_button_event(int device) const; - ButtonEvent get_button_event(int device); - bool has_pointer_event(int device) const; - PT(PointerEventList) get_pointer_events(int device); - virtual void add_window_proc( const GraphicsWindowProc* wnd_proc_object ){}; virtual void remove_window_proc( const GraphicsWindowProc* wnd_proc_object ){}; virtual void clear_window_procs(){}; @@ -146,8 +142,8 @@ protected: void system_changed_size(int x_size, int y_size); protected: - int add_input_device(const GraphicsWindowInputDevice &device); - typedef vector_GraphicsWindowInputDevice InputDevices; + int add_input_device(InputDevice *device); + typedef pvector InputDevices; InputDevices _input_devices; LightMutex _input_lock; diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index 3763d7baa4..ea26364be7 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -12,73 +12,14 @@ // //////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////// // Function: GraphicsWindowInputDevice::Default Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// INLINE GraphicsWindowInputDevice:: -GraphicsWindowInputDevice() { - LightMutexHolder holder(_lock); - _flags = 0; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_name -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE string GraphicsWindowInputDevice:: -get_name() const { - LightMutexHolder holder(_lock); - return _name; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::has_pointer -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -has_pointer() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_pointer) != 0); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::has_keyboard -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -has_keyboard() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_keyboard) != 0); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_pointer -// Access: Public -// Description: Returns the MouseData associated with the input -// device's pointer. -//////////////////////////////////////////////////////////////////// -INLINE MouseData GraphicsWindowInputDevice:: -get_pointer() const { - LightMutexHolder holder(_lock); - return _mouse_data; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_raw_pointer -// Access: Public -// Description: Returns the MouseData associated with the input -// device's pointer, in raw form (ie, prior to any -// pointer_mode interpretation). -//////////////////////////////////////////////////////////////////// -INLINE MouseData GraphicsWindowInputDevice:: -get_raw_pointer() const { - LightMutexHolder holder(_lock); - return _true_mouse_data; +GraphicsWindowInputDevice() : _host(NULL) { } //////////////////////////////////////////////////////////////////// @@ -90,140 +31,9 @@ get_raw_pointer() const { //////////////////////////////////////////////////////////////////// INLINE void GraphicsWindowInputDevice:: set_device_index(int index) { - LightMutexHolder holder(_lock); _device_index = index; } -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::enable_pointer_events -// Access: Public -// Description: Enables the generation of mouse-movement events. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -enable_pointer_events() { - LightMutexHolder holder(_lock); - _enable_pointer_events = true; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::disable_pointer_events -// Access: Public -// Description: Disables the generation of mouse-movement events. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -disable_pointer_events() { - LightMutexHolder holder(_lock); - _enable_pointer_events = false; - _pointer_events.clear(); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::button_down -// Access: Published -// Description: Records that the indicated button has been depressed. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -button_down(ButtonHandle button) { - button_down(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::button_resume_down -// Access: Published -// Description: Records that the indicated button was depressed -// earlier, and we only just detected the event after -// the fact. This is mainly useful for tracking the -// state of modifier keys. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -button_resume_down(ButtonHandle button) { - button_resume_down(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::button_up -// Access: Published -// Description: Records that the indicated button has been released. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -button_up(ButtonHandle button) { - button_up(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::keystroke -// Access: Published -// Description: Records that the indicated keystroke has been -// generated. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -keystroke(int keycode) { - keystroke(keycode, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::focus_lost -// Access: Published -// Description: This should be called when the window focus is lost, -// so that we may miss upcoming button events -// (especially "up" events) for the next period of time. -// It generates keyboard and mouse "up" events for those -// buttons that we previously sent unpaired "down" -// events, so that the Panda application will believe -// all buttons are now released. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -focus_lost() { - focus_lost(ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::raw_button_down -// Access: Published -// Description: Records that the indicated button has been depressed. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -raw_button_down(ButtonHandle button) { - raw_button_down(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::raw_button_up -// Access: Published -// Description: Records that the indicated button has been released. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -raw_button_up(ButtonHandle button) { - raw_button_up(button, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::set_pointer_in_window -// Access: Published -// Description: To be called by a particular kind of GraphicsWindow -// to indicate that the pointer is within the window, at -// the given pixel coordinates. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -set_pointer_in_window(double x, double y) { - // mutex is handled in set pointer .. convience function - set_pointer(true, x, y, ClockObject::get_global_clock()->get_frame_time()); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::set_pointer_out_of_window -// Access: Published -// Description: To be called by a particular kind of GraphicsWindow -// to indicate that the pointer is no longer within the -// window. -//////////////////////////////////////////////////////////////////// -INLINE void GraphicsWindowInputDevice:: -set_pointer_out_of_window() { - // mutex is handled in set pointer .. convience function - set_pointer(false, _mouse_data._xpos, _mouse_data._ypos, - ClockObject::get_global_clock()->get_frame_time()); -} - //////////////////////////////////////////////////////////////////// // Function: GraphicsWindowInputDevice::set_pointer_in_window // Access: Published @@ -233,8 +43,8 @@ set_pointer_out_of_window() { //////////////////////////////////////////////////////////////////// INLINE void GraphicsWindowInputDevice:: set_pointer_in_window(double x, double y, double time) { - // mutex is handled in set pointer .. convience function - set_pointer(true, x, y, time); + LightMutexHolder holder(_lock); + InputDevice::set_pointer(true, x, y, time); } //////////////////////////////////////////////////////////////////// @@ -246,36 +56,6 @@ set_pointer_in_window(double x, double y, double time) { //////////////////////////////////////////////////////////////////// INLINE void GraphicsWindowInputDevice:: set_pointer_out_of_window(double time) { - // mutex is handled in set pointer .. convience function - set_pointer(false, _mouse_data._xpos, _mouse_data._ypos, time); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::operator == -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -operator == (const GraphicsWindowInputDevice &) const { - return true; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::operator != -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -operator != (const GraphicsWindowInputDevice &) const { - return false; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::operator < -// Access: Public -// Description: -//////////////////////////////////////////////////////////////////// -INLINE bool GraphicsWindowInputDevice:: -operator < (const GraphicsWindowInputDevice &) const { - return false; + LightMutexHolder holder(_lock); + InputDevice::set_pointer_out_of_window(time); } diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index a1d4950205..e7a3c360b4 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -18,17 +18,7 @@ #include "mouseButton.h" #include "keyboardButton.h" -#define EXPCL EXPCL_PANDA_DISPLAY -#define EXPTP EXPTP_PANDA_DISPLAY -#define TYPE GraphicsWindowInputDevice -#define NAME vector_GraphicsWindowInputDevice - -#include "vector_src.cxx" - -// Tell GCC that we'll take care of the instantiation explicitly here. -#ifdef __GNUC__ -#pragma implementation -#endif +TypeHandle GraphicsWindowInputDevice::_type_handle; //////////////////////////////////////////////////////////////////// // Function: GraphicsWindowInputDevice::Constructor @@ -43,14 +33,8 @@ //////////////////////////////////////////////////////////////////// GraphicsWindowInputDevice:: GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : - _host(host), - _name(name), - _flags(flags), - _device_index(0), - _event_sequence(0), - _pointer_mode_enable(false), - _pointer_speed(1.0), - _enable_pointer_events(false) + InputDevice(name, DC_virtual, flags), + _host(host) { } @@ -60,9 +44,9 @@ GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : // Description: This named constructor returns an input device that // only has a pointing device, no keyboard. //////////////////////////////////////////////////////////////////// -GraphicsWindowInputDevice GraphicsWindowInputDevice:: +PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: pointer_only(GraphicsWindow *host, const string &name) { - return GraphicsWindowInputDevice(host, name, IDF_has_pointer); + return new GraphicsWindowInputDevice(host, name, IDF_has_pointer); } //////////////////////////////////////////////////////////////////// @@ -71,9 +55,9 @@ pointer_only(GraphicsWindow *host, const string &name) { // Description: This named constructor returns an input device that // only has a keyboard, no pointing device. //////////////////////////////////////////////////////////////////// -GraphicsWindowInputDevice GraphicsWindowInputDevice:: +PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: keyboard_only(GraphicsWindow *host, const string &name) { - return GraphicsWindowInputDevice(host, name, IDF_has_keyboard); + return new GraphicsWindowInputDevice(host, name, IDF_has_keyboard); } //////////////////////////////////////////////////////////////////// @@ -82,9 +66,9 @@ keyboard_only(GraphicsWindow *host, const string &name) { // Description: This named constructor returns an input device that // has both a keyboard and pointer. //////////////////////////////////////////////////////////////////// -GraphicsWindowInputDevice GraphicsWindowInputDevice:: +PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: pointer_and_keyboard(GraphicsWindow *host, const string &name) { - return + return new GraphicsWindowInputDevice(host, name, IDF_has_pointer | IDF_has_keyboard); } @@ -94,9 +78,8 @@ pointer_and_keyboard(GraphicsWindow *host, const string &name) { // Description: //////////////////////////////////////////////////////////////////// GraphicsWindowInputDevice:: -GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) -{ - *this = copy; +GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) { + *this = copy; } //////////////////////////////////////////////////////////////////// @@ -105,22 +88,9 @@ GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) // Description: //////////////////////////////////////////////////////////////////// void GraphicsWindowInputDevice:: -operator = (const GraphicsWindowInputDevice ©) -{ - LightMutexHolder holder(_lock); - LightMutexHolder holder1(copy._lock); +operator = (const GraphicsWindowInputDevice ©) { _host = copy._host; - _name = copy._name; - _flags = copy._flags; - _device_index = copy._device_index; - _event_sequence = copy._event_sequence; - _pointer_mode_enable = copy._pointer_mode_enable; - _pointer_speed = copy._pointer_speed; - _enable_pointer_events = copy._enable_pointer_events; - _mouse_data = copy._mouse_data; - _true_mouse_data = copy._true_mouse_data; - _button_events = copy._button_events; - _pointer_events = copy._pointer_events; + InputDevice::operator = (copy); } //////////////////////////////////////////////////////////////////// @@ -132,151 +102,6 @@ GraphicsWindowInputDevice:: ~GraphicsWindowInputDevice() { } -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::has_button_event -// Access: Public -// Description: Returns true if this device has a pending button -// event (a mouse button or keyboard button down/up), -// false otherwise. If this returns true, the -// particular event may be extracted via -// get_button_event(). -//////////////////////////////////////////////////////////////////// -bool GraphicsWindowInputDevice:: -has_button_event() const { - LightMutexHolder holder(_lock); - return !_button_events.empty(); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_button_event -// Access: Public -// Description: Assuming a previous call to has_button_event() -// returned true, this returns the pending button event. -//////////////////////////////////////////////////////////////////// -ButtonEvent GraphicsWindowInputDevice:: -get_button_event() { - LightMutexHolder holder(_lock); - ButtonEvent be = _button_events.front(); - _button_events.pop_front(); - return be; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::has_pointer_event -// Access: Public -// Description: Returns true if this device has a pending pointer -// event (a mouse movement), or false otherwise. If -// this returns true, the particular event may be -// extracted via get_pointer_event(). -//////////////////////////////////////////////////////////////////// -bool GraphicsWindowInputDevice:: -has_pointer_event() const { - LightMutexHolder holder(_lock); - return (_pointer_events != 0); -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::get_pointer_events -// Access: Public -// Description: Returns a PointerEventList containing all the recent -// pointer events. -//////////////////////////////////////////////////////////////////// -PT(PointerEventList) GraphicsWindowInputDevice:: -get_pointer_events() { - LightMutexHolder holder(_lock); - PT(PointerEventList) result = _pointer_events; - _pointer_events = 0; - return result; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::enable_pointer_mode -// Access: Public -// Description: There are two modes: raw mode, and pointer mode. -// In pointer mode, the mouse stops when it reaches the -// edges of the window. In raw mode, the mouse ignores -// the screen boundaries and can continue indefinitely, -// even into negative coordinates. In raw mode, each -// "blip" from the mouse hardware corresponds to a -// change of 1 unit in the mouse's (x,y) coordinate. -// In pointer mode, a variety of speed adjustment factors -// and concepts like "mouse acceleration" may be applied. -// -// Mouse zero represents the system mouse pointer. This -// is by definition a pointer, not a raw mouse. It is -// an error to try to enable or disable pointer mode on -// mouse zero. -//////////////////////////////////////////////////////////////////// -void GraphicsWindowInputDevice:: -enable_pointer_mode(double speed) { - LightMutexHolder holder(_lock); - nassertv(_device_index != 0); - _pointer_mode_enable = true; - _pointer_speed = speed; - _mouse_data._xpos = _host->get_x_size() * 0.5; - _mouse_data._ypos = _host->get_y_size() * 0.5; - _mouse_data._in_window = true; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::disable_pointer_mode -// Access: Public -// Description: see enable_pointer_mode. -//////////////////////////////////////////////////////////////////// -void GraphicsWindowInputDevice:: -disable_pointer_mode() { - LightMutexHolder holder(_lock); - nassertv(_device_index != 0); - _pointer_mode_enable = false; - _pointer_speed = 1.0; - _mouse_data = _true_mouse_data; -} - -//////////////////////////////////////////////////////////////////// -// Function: GraphicsWindowInputDevice::set_pointer -// Access: Published -// Description: Records that a mouse movement has taken place. -//////////////////////////////////////////////////////////////////// -void GraphicsWindowInputDevice:: -set_pointer(bool inwin, double x, double y, double time) { - LightMutexHolder holder(_lock); - - double delta_x = x - _true_mouse_data._xpos; - double delta_y = y - _true_mouse_data._ypos; - _true_mouse_data._in_window = inwin; - _true_mouse_data._xpos = x; - _true_mouse_data._ypos = y; - - if (_pointer_mode_enable) { - double pointer_x = _mouse_data._xpos; - double pointer_y = _mouse_data._ypos; - pointer_x += (delta_x * _pointer_speed); - pointer_y += (delta_y * _pointer_speed); - double xhi = _host->get_x_size(); - double yhi = _host->get_y_size(); - if (pointer_x < 0.0) pointer_x = 0.0; - if (pointer_y < 0.0) pointer_y = 0.0; - if (pointer_x > xhi) pointer_x = xhi; - if (pointer_y > yhi) pointer_y = yhi; - _mouse_data._in_window = true; - _mouse_data._xpos = pointer_x; - _mouse_data._ypos = pointer_y; - } else { - _mouse_data = _true_mouse_data; - } - - if (_enable_pointer_events) { - int seq = _event_sequence++; - if (_pointer_events == 0) { - _pointer_events = new PointerEventList(); - } - _pointer_events->add_event(_mouse_data._in_window, - _mouse_data._xpos, - _mouse_data._ypos, - seq, time); - } -} - //////////////////////////////////////////////////////////////////// // Function: GraphicsWindowInputDevice::button_down // Access: Published @@ -285,7 +110,7 @@ set_pointer(bool inwin, double x, double y, double time) { void GraphicsWindowInputDevice:: button_down(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_down, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_down, time)); _buttons_held.insert(button); } @@ -300,7 +125,7 @@ button_down(ButtonHandle button, double time) { void GraphicsWindowInputDevice:: button_resume_down(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_resume_down, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_resume_down, time)); _buttons_held.insert(button); } @@ -312,7 +137,7 @@ button_resume_down(ButtonHandle button, double time) { void GraphicsWindowInputDevice:: button_up(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_up, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_up, time)); _buttons_held.erase(button); } @@ -325,7 +150,7 @@ button_up(ButtonHandle button, double time) { void GraphicsWindowInputDevice:: keystroke(int keycode, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(keycode, time)); + _button_events->add_event(ButtonEvent(keycode, time)); } //////////////////////////////////////////////////////////////////// @@ -340,7 +165,7 @@ void GraphicsWindowInputDevice:: candidate(const wstring &candidate_string, size_t highlight_start, size_t highlight_end, size_t cursor_pos) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(candidate_string, + _button_events->add_event(ButtonEvent(candidate_string, highlight_start, highlight_end, cursor_pos)); } @@ -361,7 +186,7 @@ focus_lost(double time) { LightMutexHolder holder(_lock); ButtonsHeld::iterator bi; for (bi = _buttons_held.begin(); bi != _buttons_held.end(); ++bi) { - _button_events.push_back(ButtonEvent(*bi, ButtonEvent::T_up, time)); + _button_events->add_event(ButtonEvent(*bi, ButtonEvent::T_up, time)); } _buttons_held.clear(); } @@ -374,7 +199,7 @@ focus_lost(double time) { void GraphicsWindowInputDevice:: raw_button_down(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_raw_down, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_raw_down, time)); } //////////////////////////////////////////////////////////////////// @@ -385,5 +210,5 @@ raw_button_down(ButtonHandle button, double time) { void GraphicsWindowInputDevice:: raw_button_up(ButtonHandle button, double time) { LightMutexHolder holder(_lock); - _button_events.push_back(ButtonEvent(button, ButtonEvent::T_raw_up, time)); + _button_events->add_event(ButtonEvent(button, ButtonEvent::T_raw_up, time)); } diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index e76d48c5bc..56e62ffab0 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -16,139 +16,79 @@ #define GRAPHICSWINDOWINPUTDEVICE_H #include "pandabase.h" - -#include "buttonEvent.h" -#include "pointerEvent.h" -#include "pointerEventList.h" -#include "mouseData.h" -#include "clockObject.h" - -#include "pdeque.h" -#include "pvector.h" -#include "lightMutex.h" -#include "lightMutexHolder.h" +#include "inputDevice.h" // Forward declarations class GraphicsWindow; //////////////////////////////////////////////////////////////////// // Class : GraphicsWindowInputDevice -// Description : This is a structure representing a single input -// device that may be associated with a window. -// Typically this will be a keyboard/mouse pair, and -// there will be exactly one of these associated with -// each window, but other variants are possible. +// Description : This is a virtual input device that represents +// the keyboard and mouse pair that is associated with +// a particular window. It collects mouse and keyboard +// events from the windowing system while the window is +// in focus. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA_DISPLAY GraphicsWindowInputDevice { +class EXPCL_PANDA_DISPLAY GraphicsWindowInputDevice : public InputDevice { private: GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags); public: - static GraphicsWindowInputDevice pointer_only(GraphicsWindow *host, const string &name); - static GraphicsWindowInputDevice keyboard_only(GraphicsWindow *host, const string &name); - static GraphicsWindowInputDevice pointer_and_keyboard(GraphicsWindow *host, const string &name); + static PT(GraphicsWindowInputDevice) pointer_only(GraphicsWindow *host, const string &name); + static PT(GraphicsWindowInputDevice) keyboard_only(GraphicsWindow *host, const string &name); + static PT(GraphicsWindowInputDevice) pointer_and_keyboard(GraphicsWindow *host, const string &name); INLINE GraphicsWindowInputDevice(); GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©); void operator = (const GraphicsWindowInputDevice ©); ~GraphicsWindowInputDevice(); - INLINE string get_name() const; - INLINE bool has_pointer() const; - INLINE bool has_keyboard() const; - INLINE void set_device_index(int index); - INLINE MouseData get_pointer() const; - INLINE MouseData get_raw_pointer() const; - - INLINE void enable_pointer_events(); - INLINE void disable_pointer_events(); - - void enable_pointer_mode(double speed); - void disable_pointer_mode(); - - bool has_button_event() const; - ButtonEvent get_button_event(); - bool has_pointer_event() const; - PT(PointerEventList) get_pointer_events(); - PUBLISHED: // The following interface is for the various kinds of // GraphicsWindows to record the data incoming on the device. - INLINE void button_down(ButtonHandle button); - INLINE void button_resume_down(ButtonHandle button); - INLINE void button_up(ButtonHandle button); - INLINE void keystroke(int keycode); - INLINE void focus_lost(); - INLINE void raw_button_down(ButtonHandle button); - INLINE void raw_button_up(ButtonHandle button); - INLINE void set_pointer_in_window(double x, double y); - INLINE void set_pointer_out_of_window(); + void button_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); + void button_resume_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); + void button_up(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); - void button_down(ButtonHandle button, double time); - void button_resume_down(ButtonHandle button, double time); - void button_up(ButtonHandle button, double time); - void keystroke(int keycode, double time); + void keystroke(int keycode, double time = ClockObject::get_global_clock()->get_frame_time()); void candidate(const wstring &candidate_string, size_t highlight_start, size_t highlight_end, size_t cursor_pos); - void focus_lost(double time); - void raw_button_down(ButtonHandle button, double time); - void raw_button_up(ButtonHandle button, double time); - INLINE void set_pointer_in_window(double x, double y, double time); - INLINE void set_pointer_out_of_window(double time); - void set_pointer(bool inwin, double x, double y, double time); + void focus_lost(double time = ClockObject::get_global_clock()->get_frame_time()); -public: - // We need these methods to make VC++ happy when we try to - // instantiate a pvector. They don't do - // anything useful. - INLINE bool operator == (const GraphicsWindowInputDevice &other) const; - INLINE bool operator != (const GraphicsWindowInputDevice &other) const; - INLINE bool operator < (const GraphicsWindowInputDevice &other) const; + void raw_button_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); + void raw_button_up(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); + + INLINE void set_pointer_in_window(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); private: - enum InputDeviceFlags { - IDF_has_pointer = 0x01, - IDF_has_keyboard = 0x02 - }; - typedef pdeque ButtonEvents; - - LightMutex _lock; - GraphicsWindow *_host; - - string _name; - int _flags; int _device_index; - int _event_sequence; - - bool _pointer_mode_enable; - double _pointer_speed; - - bool _enable_pointer_events; - MouseData _mouse_data; - MouseData _true_mouse_data; - ButtonEvents _button_events; - PT(PointerEventList) _pointer_events; typedef pset ButtonsHeld; ButtonsHeld _buttons_held; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + InputDevice::init_type(); + register_type(_type_handle, "GraphicsWindowInputDevice", + InputDevice::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; }; #include "graphicsWindowInputDevice.I" -#define EXPCL EXPCL_PANDA_DISPLAY -#define EXPTP EXPTP_PANDA_DISPLAY -#define TYPE GraphicsWindowInputDevice -#define NAME vector_GraphicsWindowInputDevice - -#include "vector_src.h" - -// Tell GCC that we'll take care of the instantiation explicitly here. -#ifdef __GNUC__ -#pragma interface -#endif - #endif diff --git a/panda/src/device/mouseAndKeyboard.cxx b/panda/src/display/mouseAndKeyboard.cxx similarity index 88% rename from panda/src/device/mouseAndKeyboard.cxx rename to panda/src/display/mouseAndKeyboard.cxx index e8c102746e..7666b63404 100644 --- a/panda/src/device/mouseAndKeyboard.cxx +++ b/panda/src/display/mouseAndKeyboard.cxx @@ -41,7 +41,6 @@ MouseAndKeyboard(GraphicsWindow *window, int device, const string &name) : _pixel_xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); _pixel_size = new EventStoreVec2(LPoint2(0.0f, 0.0f)); _xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); - _button_events = new ButtonEventList; } //////////////////////////////////////////////////////////////////// @@ -93,20 +92,18 @@ get_source_device() const { void MouseAndKeyboard:: do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { - if (_window->has_button_event(_device)) { - // Fill up the button events. - _button_events->clear(); - while (_window->has_button_event(_device)) { - ButtonEvent be = _window->get_button_event(_device); - _button_events->add_event(be); - } - output.set_data(_button_events_output, EventParameter(_button_events)); + + PT(InputDevice) device = _window->get_input_device(_device); + + if (device->has_button_event()) { + PT(ButtonEventList) bel = device->get_button_events(); + output.set_data(_button_events_output, EventParameter(bel)); } - if (_window->has_pointer_event(_device)) { - PT(PointerEventList) pel = _window->get_pointer_events(_device); + if (device->has_pointer_event()) { + PT(PointerEventList) pel = device->get_pointer_events(); output.set_data(_pointer_events_output, EventParameter(pel)); } - + // Get the window size. WindowProperties properties = _window->get_properties(); if (properties.has_size()) { @@ -116,18 +113,18 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, _pixel_size->set_value(LPoint2(w, h)); output.set_data(_pixel_size_output, EventParameter(_pixel_size)); - if (_window->has_pointer(_device)) { - const MouseData &mdata = _window->get_pointer(_device); + if (device->has_pointer()) { + const MouseData &mdata = device->get_pointer(); if (mdata._in_window) { // Get mouse motion in pixels. _pixel_xy->set_value(LPoint2(mdata._xpos, mdata._ypos)); output.set_data(_pixel_xy_output, EventParameter(_pixel_xy)); - + // Normalize pixel motion to range [-1,1]. PN_stdfloat xf = (PN_stdfloat)(2 * mdata._xpos) / (PN_stdfloat)w - 1.0f; PN_stdfloat yf = 1.0f - (PN_stdfloat)(2 * mdata._ypos) / (PN_stdfloat)h; - + _xy->set_value(LPoint2(xf, yf)); output.set_data(_xy_output, EventParameter(_xy)); } diff --git a/panda/src/device/mouseAndKeyboard.h b/panda/src/display/mouseAndKeyboard.h similarity index 98% rename from panda/src/device/mouseAndKeyboard.h rename to panda/src/display/mouseAndKeyboard.h index 939a04aa8a..f39674900a 100644 --- a/panda/src/device/mouseAndKeyboard.h +++ b/panda/src/display/mouseAndKeyboard.h @@ -51,7 +51,7 @@ PUBLISHED: PT(GraphicsWindow) get_source_window() const; int get_source_device() const; - + protected: // Inherited from DataNode virtual void do_transmit_data(DataGraphTraverser *trav, @@ -69,7 +69,6 @@ private: PT(EventStoreVec2) _pixel_xy; PT(EventStoreVec2) _pixel_size; PT(EventStoreVec2) _xy; - PT(ButtonEventList) _button_events; PT(GraphicsWindow) _window; int _device; diff --git a/panda/src/display/p3display_composite2.cxx b/panda/src/display/p3display_composite2.cxx index b3ba58995b..c2d47f7aad 100644 --- a/panda/src/display/p3display_composite2.cxx +++ b/panda/src/display/p3display_composite2.cxx @@ -5,6 +5,7 @@ #include "graphicsWindowProc.cxx" #include "graphicsWindowProcCallbackData.cxx" #include "graphicsWindowInputDevice.cxx" +#include "mouseAndKeyboard.cxx" #include "nativeWindowHandle.cxx" #include "parasiteBuffer.cxx" #include "standardMunger.cxx" diff --git a/panda/src/putil/config_util.cxx b/panda/src/putil/config_util.cxx index 44ac7bd6a4..8610341402 100644 --- a/panda/src/putil/config_util.cxx +++ b/panda/src/putil/config_util.cxx @@ -32,6 +32,7 @@ #include "datagram.h" #include "doubleBitMask.h" #include "factoryParam.h" +#include "gamepadButton.h" #include "namable.h" #include "nodeCachedReferenceCount.h" #include "paramValue.h" @@ -225,6 +226,7 @@ init_libputil() { WritableConfigurable::init_type(); WritableParam::init_type(); + GamepadButton::init_gamepad_buttons(); KeyboardButton::init_keyboard_buttons(); MouseButton::init_mouse_buttons(); diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx new file mode 100644 index 0000000000..ed8d41a435 --- /dev/null +++ b/panda/src/putil/gamepadButton.cxx @@ -0,0 +1,75 @@ +// Filename: gamepadButton.cxx +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "gamepadButton.h" +#include "buttonRegistry.h" + +#define DEFINE_GAMEPAD_BUTTON_HANDLE(KeyName) \ + static ButtonHandle _##KeyName; \ + ButtonHandle GamepadButton::KeyName() { return _##KeyName; } + +DEFINE_GAMEPAD_BUTTON_HANDLE(lstick) +DEFINE_GAMEPAD_BUTTON_HANDLE(rstick) +DEFINE_GAMEPAD_BUTTON_HANDLE(lshoulder) +DEFINE_GAMEPAD_BUTTON_HANDLE(rshoulder) +DEFINE_GAMEPAD_BUTTON_HANDLE(ltrigger) +DEFINE_GAMEPAD_BUTTON_HANDLE(rtrigger) + +DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_left) +DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_right) +DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_up) +DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_down) + +DEFINE_GAMEPAD_BUTTON_HANDLE(back) +DEFINE_GAMEPAD_BUTTON_HANDLE(guide) +DEFINE_GAMEPAD_BUTTON_HANDLE(start) + +DEFINE_GAMEPAD_BUTTON_HANDLE(action_a) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_b) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_c) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_x) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_y) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_z) + +//////////////////////////////////////////////////////////////////// +// Function: GamepadButton::init_gamepad_buttons +// Access: Public, Static +// Description: This is intended to be called only once, by the +// static initialization performed in config_util.cxx. +//////////////////////////////////////////////////////////////////// +void GamepadButton:: +init_gamepad_buttons() { + ButtonRegistry::ptr()->register_button(_lstick, "lstick"); + ButtonRegistry::ptr()->register_button(_rstick, "rstick"); + ButtonRegistry::ptr()->register_button(_lshoulder, "lshoulder"); + ButtonRegistry::ptr()->register_button(_rshoulder, "rshoulder"); + ButtonRegistry::ptr()->register_button(_ltrigger, "ltrigger"); + ButtonRegistry::ptr()->register_button(_rtrigger, "rtrigger"); + + ButtonRegistry::ptr()->register_button(_dpad_left, "dpad_left"); + ButtonRegistry::ptr()->register_button(_dpad_right, "dpad_right"); + ButtonRegistry::ptr()->register_button(_dpad_up, "dpad_up"); + ButtonRegistry::ptr()->register_button(_dpad_down, "dpad_down"); + + ButtonRegistry::ptr()->register_button(_back, "back"); + ButtonRegistry::ptr()->register_button(_guide, "guide"); + ButtonRegistry::ptr()->register_button(_start, "start"); + + ButtonRegistry::ptr()->register_button(_action_a, "action_a"); + ButtonRegistry::ptr()->register_button(_action_b, "action_b"); + ButtonRegistry::ptr()->register_button(_action_c, "action_c"); + ButtonRegistry::ptr()->register_button(_action_x, "action_x"); + ButtonRegistry::ptr()->register_button(_action_y, "action_y"); + ButtonRegistry::ptr()->register_button(_action_z, "action_z"); +} diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h new file mode 100644 index 0000000000..4d6b7afa88 --- /dev/null +++ b/panda/src/putil/gamepadButton.h @@ -0,0 +1,57 @@ +// Filename: gamepadButton.h +// Created by: rdb (21Aug15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef GAMEPADBUTTON_H +#define GAMEPADBUTTON_H + +#include "pandabase.h" + +#include "buttonHandle.h" + +//////////////////////////////////////////////////////////////////// +// Class : GamepadButton +// Description : This class is just used as a convenient namespace for +// grouping all of these handy functions that return +// buttons which map to gamepad buttons. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_PUTIL GamepadButton { +PUBLISHED: + static ButtonHandle lstick(); + static ButtonHandle rstick(); + static ButtonHandle lshoulder(); + static ButtonHandle rshoulder(); + static ButtonHandle ltrigger(); + static ButtonHandle rtrigger(); + + static ButtonHandle dpad_left(); + static ButtonHandle dpad_right(); + static ButtonHandle dpad_up(); + static ButtonHandle dpad_down(); + + static ButtonHandle back(); + static ButtonHandle guide(); + static ButtonHandle start(); + + static ButtonHandle action_a(); + static ButtonHandle action_b(); + static ButtonHandle action_c(); + static ButtonHandle action_x(); + static ButtonHandle action_y(); + static ButtonHandle action_z(); + +public: + static void init_gamepad_buttons(); +}; + +#endif diff --git a/panda/src/putil/p3putil_composite1.cxx b/panda/src/putil/p3putil_composite1.cxx index 3fd7673773..294f465629 100644 --- a/panda/src/putil/p3putil_composite1.cxx +++ b/panda/src/putil/p3putil_composite1.cxx @@ -28,4 +28,3 @@ #include "factoryBase.cxx" #include "factoryParam.cxx" #include "factoryParams.cxx" -#include "globalPointerRegistry.cxx" diff --git a/panda/src/putil/p3putil_composite2.cxx b/panda/src/putil/p3putil_composite2.cxx index a4ce5c9cfc..9b0f01d851 100644 --- a/panda/src/putil/p3putil_composite2.cxx +++ b/panda/src/putil/p3putil_composite2.cxx @@ -1,3 +1,5 @@ +#include "gamepadButton.cxx" +#include "globalPointerRegistry.cxx" #include "ioPtaDatagramFloat.cxx" #include "ioPtaDatagramInt.cxx" #include "ioPtaDatagramShort.cxx" diff --git a/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm b/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm index 876de6450d..f14641becf 100644 --- a/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm +++ b/panda/src/tinydisplay/tinyOsxGraphicsWindow.mm @@ -419,7 +419,7 @@ OSStatus TinyOsxGraphicsWindow::handleTextInput (EventHandlerCallRef myHandler, } for (unsigned int x = 0; x < actualSize/sizeof(UniChar); ++x) { - _input_devices[0].keystroke(text[x]); + _input_devices[0]->keystroke(text[x]); } DisposePtr((char *)text); } @@ -492,10 +492,10 @@ TinyOsxGraphicsWindow::TinyOsxGraphicsWindow(GraphicsEngine *engine, GraphicsPip _current_icon(NULL), _ID(id_seed++), _originalMode(NULL) { - GraphicsWindowInputDevice device = + PT(InputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard/mouse"); _input_devices.push_back(device); - _input_devices[0].set_pointer_in_window(0, 0); + device->set_pointer_in_window(0, 0); _last_key_modifiers = 0; _last_buttons = 0; @@ -579,7 +579,7 @@ bool TinyOsxGraphicsWindow::set_icon_filename(const Filename &icon_filename) { //////////////////////////////////////////////////////////////////// void TinyOsxGraphicsWindow:: set_pointer_in_window(int x, int y) { - _input_devices[0].set_pointer_in_window(x, y); + _input_devices[0]->set_pointer_in_window(x, y); if (_cursor_hidden != _display_hide_cursor) { if (_cursor_hidden) { @@ -600,7 +600,7 @@ set_pointer_in_window(int x, int y) { //////////////////////////////////////////////////////////////////// void TinyOsxGraphicsWindow:: set_pointer_out_of_window() { - _input_devices[0].set_pointer_out_of_window(); + _input_devices[0]->set_pointer_out_of_window(); if (_display_hide_cursor) { CGDisplayShowCursor(kCGDirectMainDisplay); @@ -1333,13 +1333,13 @@ void TinyOsxGraphicsWindow::SystemPointToLocalPoint(Point &qdGlobalPoint) { _wheel_delta += this_wheel_delta; SInt32 wheel_scale = osx_mouse_wheel_scale; while (_wheel_delta > wheel_scale) { - _input_devices[0].button_down(MouseButton::wheel_up()); - _input_devices[0].button_up(MouseButton::wheel_up()); + _input_devices[0]->button_down(MouseButton::wheel_up()); + _input_devices[0]->button_up(MouseButton::wheel_up()); _wheel_delta -= wheel_scale; } while (_wheel_delta < -wheel_scale) { - _input_devices[0].button_down(MouseButton::wheel_down()); - _input_devices[0].button_up(MouseButton::wheel_down()); + _input_devices[0]->button_down(MouseButton::wheel_down()); + _input_devices[0]->button_up(MouseButton::wheel_down()); _wheel_delta += wheel_scale; } } @@ -1519,25 +1519,25 @@ HandleButtonDelta(UInt32 new_buttons) { if (changed & 0x01) { if (new_buttons & 0x01) { - _input_devices[0].button_down(MouseButton::one()); + _input_devices[0]->button_down(MouseButton::one()); } else { - _input_devices[0].button_up(MouseButton::one()); + _input_devices[0]->button_up(MouseButton::one()); } } if (changed & 0x04) { if (new_buttons & 0x04) { - _input_devices[0].button_down(MouseButton::two()); + _input_devices[0]->button_down(MouseButton::two()); } else { - _input_devices[0].button_up(MouseButton::two()); + _input_devices[0]->button_up(MouseButton::two()); } } if (changed & 0x02) { if (new_buttons & 0x02) { - _input_devices[0].button_down(MouseButton::three()); + _input_devices[0]->button_down(MouseButton::three()); } else { - _input_devices[0].button_up(MouseButton::three()); + _input_devices[0]->button_up(MouseButton::three()); } } @@ -1745,7 +1745,7 @@ void TinyOsxGraphicsWindow::set_properties_now(WindowProperties &properties) { if (properties.has_cursor_hidden()) { _properties.set_cursor_hidden(properties.get_cursor_hidden()); _cursor_hidden = properties.get_cursor_hidden(); - if (_cursor_hidden && _input_devices[0].has_pointer()) { + if (_cursor_hidden && _input_devices[0]->has_pointer()) { if (!_display_hide_cursor) { CGDisplayHideCursor(kCGDirectMainDisplay); _display_hide_cursor = true; diff --git a/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx b/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx index 47631c6321..2da9337c1f 100644 --- a/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx +++ b/panda/src/tinydisplay/tinySDLGraphicsWindow.cxx @@ -46,9 +46,7 @@ TinySDLGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _pitch = 0; update_pixel_factor(); - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); - add_input_device(device); + add_input_device(GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse")); } //////////////////////////////////////////////////////////////////// @@ -184,35 +182,35 @@ process_events() { switch(evt.type) { case SDL_KEYDOWN: if (evt.key.keysym.unicode) { - _input_devices[0].keystroke(evt.key.keysym.unicode); + _input_devices[0]->keystroke(evt.key.keysym.unicode); } button = get_keyboard_button(evt.key.keysym.sym); if (button != ButtonHandle::none()) { - _input_devices[0].button_down(button); + _input_devices[0]->button_down(button); } break; case SDL_KEYUP: button = get_keyboard_button(evt.key.keysym.sym); if (button != ButtonHandle::none()) { - _input_devices[0].button_up(button); + _input_devices[0]->button_up(button); } break; case SDL_MOUSEBUTTONDOWN: button = get_mouse_button(evt.button.button); - _input_devices[0].set_pointer_in_window(evt.button.x, evt.button.y); - _input_devices[0].button_down(button); + _input_devices[0]->set_pointer_in_window(evt.button.x, evt.button.y); + _input_devices[0]->button_down(button); break; case SDL_MOUSEBUTTONUP: button = get_mouse_button(evt.button.button); - _input_devices[0].set_pointer_in_window(evt.button.x, evt.button.y); - _input_devices[0].button_up(button); + _input_devices[0]->set_pointer_in_window(evt.button.x, evt.button.y); + _input_devices[0]->button_up(button); break; case SDL_MOUSEMOTION: - _input_devices[0].set_pointer_in_window(evt.motion.x, evt.motion.y); + _input_devices[0]->set_pointer_in_window(evt.motion.x, evt.motion.y); break; case SDL_VIDEORESIZE: diff --git a/panda/src/tinydisplay/tinyXGraphicsWindow.cxx b/panda/src/tinydisplay/tinyXGraphicsWindow.cxx index 924ababedb..0b6cf0b48e 100644 --- a/panda/src/tinydisplay/tinyXGraphicsWindow.cxx +++ b/panda/src/tinydisplay/tinyXGraphicsWindow.cxx @@ -215,9 +215,7 @@ process_events() { if (_xwindow == (X11_Window)0) { return; } - - poll_raw_mice(); - + XEvent event; XKeyEvent keyrelease_event; bool got_keyrelease_event = false; @@ -294,18 +292,18 @@ process_events() { case ButtonPress: // This refers to the mouse buttons. button = get_mouse_button(event.xbutton); - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); - _input_devices[0].button_down(button); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->button_down(button); break; case ButtonRelease: button = get_mouse_button(event.xbutton); - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); - _input_devices[0].button_up(button); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->button_up(button); break; case MotionNotify: - _input_devices[0].set_pointer_in_window(event.xmotion.x, event.xmotion.y); + _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y); break; case KeyPress: @@ -322,11 +320,11 @@ process_events() { break; case EnterNotify: - _input_devices[0].set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); + _input->set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); break; case LeaveNotify: - _input_devices[0].set_pointer_out_of_window(); + _input->set_pointer_out_of_window(); break; case FocusIn: @@ -335,7 +333,7 @@ process_events() { break; case FocusOut: - _input_devices[0].focus_lost(); + _input->focus_lost(); properties.set_foreground(false); system_changed_properties(properties); break; diff --git a/panda/src/vrpn/vrpnAnalog.cxx b/panda/src/vrpn/vrpnAnalog.cxx index 40e55796a4..377574b6fe 100644 --- a/panda/src/vrpn/vrpnAnalog.cxx +++ b/panda/src/vrpn/vrpnAnalog.cxx @@ -115,7 +115,6 @@ vrpn_analog_callback(void *userdata, const vrpn_ANALOGCB info) { Devices::iterator di; for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnAnalogDevice *device = (*di); - device->acquire(); for (int i = 0; i < info.num_channel; i++) { if (vrpn_cat.is_debug()) { if (device->get_control_state(i) != info.channel[i]) { @@ -125,6 +124,5 @@ vrpn_analog_callback(void *userdata, const vrpn_ANALOGCB info) { } device->set_control_state(i, info.channel[i]); } - device->unlock(); } } diff --git a/panda/src/vrpn/vrpnButton.cxx b/panda/src/vrpn/vrpnButton.cxx index 444fea8708..d6bca1cf4b 100644 --- a/panda/src/vrpn/vrpnButton.cxx +++ b/panda/src/vrpn/vrpnButton.cxx @@ -119,8 +119,6 @@ vrpn_button_callback(void *userdata, const vrpn_BUTTONCB info) { Devices::iterator di; for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnButtonDevice *device = (*di); - device->acquire(); device->set_button_state(info.button, info.state != 0); - device->unlock(); } } diff --git a/panda/src/vrpn/vrpnDial.cxx b/panda/src/vrpn/vrpnDial.cxx index 34d4ae549a..0c958d98ab 100644 --- a/panda/src/vrpn/vrpnDial.cxx +++ b/panda/src/vrpn/vrpnDial.cxx @@ -120,8 +120,6 @@ vrpn_dial_callback(void *userdata, const vrpn_DIALCB info) { Devices::iterator di; for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnDialDevice *device = (*di); - device->acquire(); device->push_dial(info.dial, info.change); - device->unlock(); } } diff --git a/panda/src/vrpn/vrpnTracker.cxx b/panda/src/vrpn/vrpnTracker.cxx index c99e95b513..37cc8c03b8 100644 --- a/panda/src/vrpn/vrpnTracker.cxx +++ b/panda/src/vrpn/vrpnTracker.cxx @@ -123,11 +123,10 @@ vrpn_position_callback(void *userdata, const vrpn_TRACKERCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_position) { - device->acquire(); - device->_data.set_time(VrpnClient::convert_to_secs(info.msg_time)); - device->_data.set_pos(LPoint3(info.pos[0], info.pos[1], info.pos[2])); - device->_data.set_orient(LOrientation(info.quat[3], info.quat[0], info.quat[1], info.quat[2])); - device->unlock(); + device->set_tracker(LPoint3(info.pos[0], info.pos[1], info.pos[2]), + LOrientation(info.quat[3], info.quat[0], + info.quat[1], info.quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } @@ -152,13 +151,10 @@ vrpn_velocity_callback(void *userdata, const vrpn_TRACKERVELCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_velocity) { - device->acquire(); - device->_data.set_time(VrpnClient::convert_to_secs(info.msg_time)); - device->_data.set_pos(LPoint3(info.vel[0], info.vel[1], info.vel[2])); - device->_data.set_orient(LOrientation(info.vel_quat[3], info.vel_quat[0], - info.vel_quat[1], info.vel_quat[2])); - device->_data.set_dt(info.vel_quat_dt); - device->unlock(); + device->set_tracker(LPoint3(info.vel[0], info.vel[1], info.vel[2]), + LOrientation(info.vel_quat[3], info.vel_quat[0], + info.vel_quat[1], info.vel_quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } @@ -183,13 +179,10 @@ vrpn_acceleration_callback(void *userdata, const vrpn_TRACKERACCCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_acceleration) { - device->acquire(); - device->_data.set_time(VrpnClient::convert_to_secs(info.msg_time)); - device->_data.set_pos(LPoint3(info.acc[0], info.acc[1], info.acc[2])); - device->_data.set_orient(LOrientation(info.acc_quat[3], info.acc_quat[0], - info.acc_quat[1], info.acc_quat[2])); - device->_data.set_dt(info.acc_quat_dt); - device->unlock(); + device->set_tracker(LPoint3(info.acc[0], info.acc[1], info.acc[2]), + LOrientation(info.acc_quat[3], info.acc_quat[0], + info.acc_quat[1], info.acc_quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index f270a70f61..4e04206426 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -2786,9 +2786,9 @@ handle_raw_input(HRAWINPUT hraw) { if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { _input_devices[i].set_pointer_in_window(adjx, adjy); } else { - int oldx = _input_devices[i].get_raw_pointer().get_x(); - int oldy = _input_devices[i].get_raw_pointer().get_y(); - _input_devices[i].set_pointer_in_window(oldx + adjx, oldy + adjy); + //int oldx = _input_devices[i].get_raw_pointer().get_x(); + //int oldy = _input_devices[i].get_raw_pointer().get_y(); + //_input_devices[i].set_pointer_in_window(oldx + adjx, oldy + adjy); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) { diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 326953d0c4..8e9f6252bb 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -28,14 +28,10 @@ #include "nativeWindowHandle.h" #include "virtualFileSystem.h" #include "get_x11.h" +#include "evdevInputDevice.h" -#include -#include #include - -#ifdef PHAVE_LINUX_INPUT_H -#include -#endif +#include #ifdef HAVE_XCURSOR static int xcursor_read(XcursorFile *file, unsigned char *buf, int len) { @@ -69,8 +65,6 @@ static int xcursor_seek(XcursorFile *file, long offset, int whence) { TypeHandle x11GraphicsWindow::_type_handle; -#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7))) - //////////////////////////////////////////////////////////////////// // Function: x11GraphicsWindow::Constructor // Access: Public @@ -107,9 +101,9 @@ x11GraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _override_redirect = False; _wm_delete_window = x11_pipe->_wm_delete_window; - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); add_input_device(device); + _input = device; } //////////////////////////////////////////////////////////////////// @@ -143,26 +137,26 @@ move_pointer(int device, int x, int y) { if (device == 0) { // Move the system mouse pointer. if (!_properties.get_foreground() || - !_input_devices[0].get_pointer().get_in_window()) { + !_input->get_pointer().get_in_window()) { // If the window doesn't have input focus, or the mouse isn't // currently within the window, forget it. return false; } - const MouseData &md = _input_devices[0].get_pointer(); + const MouseData &md = _input->get_pointer(); if (!md.get_in_window() || md.get_x() != x || md.get_y() != y) { if (!_dga_mouse_enabled) { XWarpPointer(_display, None, _xwindow, 0, 0, 0, 0, x, y); } - _input_devices[0].set_pointer_in_window(x, y); + _input->set_pointer_in_window(x, y); } return true; } else { // Move a raw mouse. - if ((device < 1)||(device >= _input_devices.size())) { + if (device < 1 || device >= _input_devices.size()) { return false; } - _input_devices[device].set_pointer_in_window(x, y); + //_input_devices[device]->set_pointer_in_window(x, y); return true; } } @@ -249,8 +243,6 @@ process_events() { return; } - poll_raw_mice(); - XEvent event; XKeyEvent keyrelease_event; bool got_keyrelease_event = false; @@ -311,25 +303,25 @@ process_events() { // This refers to the mouse buttons. button = get_mouse_button(event.xbutton); if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); } - _input_devices[0].button_down(button); + _input->button_down(button); break; case ButtonRelease: button = get_mouse_button(event.xbutton); if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); } - _input_devices[0].button_up(button); + _input->button_up(button); break; case MotionNotify: if (_dga_mouse_enabled) { - const MouseData &md = _input_devices[0].get_raw_pointer(); - _input_devices[0].set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root); + const MouseData &md = _input->get_pointer(); + _input->set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root); } else { - _input_devices[0].set_pointer_in_window(event.xmotion.x, event.xmotion.y); + _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y); } break; @@ -348,15 +340,15 @@ process_events() { case EnterNotify: if (_dga_mouse_enabled) { - const MouseData &md = _input_devices[0].get_raw_pointer(); - _input_devices[0].set_pointer_in_window(md.get_x(), md.get_y()); + const MouseData &md = _input->get_pointer(); + _input->set_pointer_in_window(md.get_x(), md.get_y()); } else { - _input_devices[0].set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); + _input->set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); } break; case LeaveNotify: - _input_devices[0].set_pointer_out_of_window(); + _input->set_pointer_out_of_window(); break; case FocusIn: @@ -365,7 +357,7 @@ process_events() { break; case FocusOut: - _input_devices[0].focus_lost(); + _input->focus_lost(); properties.set_foreground(false); changed_properties = true; break; @@ -751,7 +743,7 @@ set_properties_now(WindowProperties &properties) { XQueryPointer(_display, _xwindow, &event.xbutton.root, &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state); - _input_devices[0].set_pointer_in_window(event.xbutton.x, event.xbutton.y); + _input->set_pointer_in_window(event.xbutton.x, event.xbutton.y); } } else { x11display_cat.info() << "XF86DGA extension not available\n"; @@ -1295,66 +1287,43 @@ open_raw_mice() { bool any_mice = false; for (int i=0; i<64; i++) { - uint8_t evtypes[EV_MAX/8 + 1]; ostringstream fnb; fnb << "/dev/input/event" << i; string fn = fnb.str(); int fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK, 0); if (fd >= 0) { - any_present = true; - char name[256]; - char phys[256]; - char uniq[256]; - if ((ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0)|| - (ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys) < 0)|| - (ioctl(fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0)|| - (ioctl(fd, EVIOCGBIT(0, EV_MAX), &evtypes) < 0)) { - close(fd); - x11display_cat.error() << - "Opening raw mice: ioctl failed on " << fn << "\n"; - } else { - if (test_bit(EV_REL, evtypes) || test_bit(EV_ABS, evtypes)) { - for (char *p=name; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } - } - for (char *p=uniq; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } - } - string full_id = ((string)name) + "." + uniq; - MouseDeviceInfo inf; - inf._fd = fd; - inf._input_device_index = _input_devices.size(); - inf._io_buffer = ""; - _mouse_device_info.push_back(inf); - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_only(this, full_id); - add_input_device(device); - x11display_cat.info() << "Raw mouse " << - inf._input_device_index << " detected: " << full_id << "\n"; - any_mice = true; - } else { - close(fd); - } + EvdevInputDevice *device = new EvdevInputDevice(fd); + nassertd(device != NULL) continue; + + if (device->has_pointer()) { + add_input_device(device); + + x11display_cat.info() + << "Raw mouse " << _input_devices.size() + << " detected: " << device->get_name() << "\n"; + + any_mice = true; + any_present = true; } } else { - if ((errno == ENOENT)||(errno == ENOTDIR)) { + if (errno == ENOENT || errno == ENOTDIR) { break; } else { any_present = true; - x11display_cat.error() << - "Opening raw mice: " << strerror(errno) << " " << fn << "\n"; + x11display_cat.error() + << "Opening raw mice: " << strerror(errno) << " " << fn << "\n"; } } } - if (!any_present) { + if (any_mice) { + _properties.set_raw_mice(true); + + } else if (!any_present) { x11display_cat.error() << "Opening raw mice: files not found: /dev/input/event*\n"; - } else if (!any_mice) { + + } else { x11display_cat.error() << "Opening raw mice: no mouse devices detected in /dev/input/event*\n"; } @@ -1364,69 +1333,6 @@ open_raw_mice() { #endif } -//////////////////////////////////////////////////////////////////// -// Function: x11GraphicsWindow::poll_raw_mice -// Access: Private -// Description: Reads events from the raw mouse device files. -//////////////////////////////////////////////////////////////////// -void x11GraphicsWindow:: -poll_raw_mice() { -#ifdef PHAVE_LINUX_INPUT_H - for (int di = 0; di < _mouse_device_info.size(); ++di) { - MouseDeviceInfo &inf = _mouse_device_info[di]; - - // Read all bytes into buffer. - if (inf._fd >= 0) { - while (1) { - char tbuf[1024]; - int nread = read(inf._fd, tbuf, sizeof(tbuf)); - if (nread > 0) { - inf._io_buffer += string(tbuf, nread); - } else { - if ((nread < 0) && ((errno == EWOULDBLOCK) || (errno==EAGAIN))) { - break; - } - close(inf._fd); - inf._fd = -1; - break; - } - } - } - - // Process events. - int nevents = inf._io_buffer.size() / sizeof(struct input_event); - if (nevents == 0) { - continue; - } - const input_event *events = (const input_event *)(inf._io_buffer.c_str()); - GraphicsWindowInputDevice &dev = _input_devices[inf._input_device_index]; - int x = dev.get_raw_pointer().get_x(); - int y = dev.get_raw_pointer().get_y(); - for (int i = 0; i < nevents; i++) { - if (events[i].type == EV_REL) { - if (events[i].code == REL_X) x += events[i].value; - if (events[i].code == REL_Y) y += events[i].value; - } else if (events[i].type == EV_ABS) { - if (events[i].code == ABS_X) x = events[i].value; - if (events[i].code == ABS_Y) y = events[i].value; - } else if (events[i].type == EV_KEY) { - if ((events[i].code >= BTN_MOUSE) && (events[i].code < BTN_MOUSE + 8)) { - int btn = events[i].code - BTN_MOUSE; - dev.set_pointer_in_window(x, y); - if (events[i].value) { - dev.button_down(MouseButton::button(btn)); - } else { - dev.button_up(MouseButton::button(btn)); - } - } - } - } - inf._io_buffer.erase(0, nevents * sizeof(struct input_event)); - dev.set_pointer_in_window(x, y); - } -#endif -} - //////////////////////////////////////////////////////////////////// // Function: x11GraphicsWindow::handle_keystroke // Access: Private @@ -1436,7 +1342,7 @@ poll_raw_mice() { void x11GraphicsWindow:: handle_keystroke(XKeyEvent &event) { if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.x, event.y); + _input->set_pointer_in_window(event.x, event.y); } if (_ic) { @@ -1454,14 +1360,14 @@ handle_keystroke(XKeyEvent &event) { // Now each of the returned wide characters represents a // keystroke. for (int i = 0; i < len; i++) { - _input_devices[0].keystroke(buffer[i]); + _input->keystroke(buffer[i]); } } else { // Without an input context, just get the ascii keypress. ButtonHandle button = get_button(event, true); if (button.has_ascii_equivalent()) { - _input_devices[0].keystroke(button.get_ascii_equivalent()); + _input->keystroke(button.get_ascii_equivalent()); } } } @@ -1475,30 +1381,32 @@ handle_keystroke(XKeyEvent &event) { void x11GraphicsWindow:: handle_keypress(XKeyEvent &event) { if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.x, event.y); + _input->set_pointer_in_window(event.x, event.y); } // Now get the raw unshifted button. ButtonHandle button = get_button(event, false); if (button != ButtonHandle::none()) { if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) { - _input_devices[0].button_down(KeyboardButton::control()); + _input->button_down(KeyboardButton::control()); } if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) { - _input_devices[0].button_down(KeyboardButton::shift()); + _input->button_down(KeyboardButton::shift()); } if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) { - _input_devices[0].button_down(KeyboardButton::alt()); + _input->button_down(KeyboardButton::alt()); } if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) { - _input_devices[0].button_down(KeyboardButton::meta()); + _input->button_down(KeyboardButton::meta()); } - _input_devices[0].button_down(button); + _input->button_down(button); } - ButtonHandle raw_button = map_raw_button(event.keycode); - if (raw_button != ButtonHandle::none()) { - _input_devices[0].raw_button_down(raw_button); + if (event.keycode >= 9 && event.keycode <= 135) { + ButtonHandle raw_button = map_raw_button(event.keycode); + if (raw_button != ButtonHandle::none()) { + _input->raw_button_down(raw_button); + } } } @@ -1511,30 +1419,32 @@ handle_keypress(XKeyEvent &event) { void x11GraphicsWindow:: handle_keyrelease(XKeyEvent &event) { if (!_dga_mouse_enabled) { - _input_devices[0].set_pointer_in_window(event.x, event.y); + _input->set_pointer_in_window(event.x, event.y); } // Now get the raw unshifted button. ButtonHandle button = get_button(event, false); if (button != ButtonHandle::none()) { if (button == KeyboardButton::lcontrol() || button == KeyboardButton::rcontrol()) { - _input_devices[0].button_up(KeyboardButton::control()); + _input->button_up(KeyboardButton::control()); } if (button == KeyboardButton::lshift() || button == KeyboardButton::rshift()) { - _input_devices[0].button_up(KeyboardButton::shift()); + _input->button_up(KeyboardButton::shift()); } if (button == KeyboardButton::lalt() || button == KeyboardButton::ralt()) { - _input_devices[0].button_up(KeyboardButton::alt()); + _input->button_up(KeyboardButton::alt()); } if (button == KeyboardButton::lmeta() || button == KeyboardButton::rmeta()) { - _input_devices[0].button_up(KeyboardButton::meta()); + _input->button_up(KeyboardButton::meta()); } - _input_devices[0].button_up(button); + _input->button_up(button); } - ButtonHandle raw_button = map_raw_button(event.keycode); - if (raw_button != ButtonHandle::none()) { - _input_devices[0].raw_button_up(raw_button); + if (event.keycode >= 9 && event.keycode <= 135) { + ButtonHandle raw_button = map_raw_button(event.keycode); + if (raw_button != ButtonHandle::none()) { + _input->raw_button_up(raw_button); + } } } @@ -1617,7 +1527,7 @@ get_button(XKeyEvent &key_event, bool allow_shift) { // can do this in just the ASCII set, because we handle // international keyboards elsewhere (via an input context). if ((key_event.state & (ShiftMask | LockMask)) != 0) { - if (key >= XK_a and key <= XK_z) { + if (key >= XK_a && key <= XK_z) { key += (XK_A - XK_a); } } @@ -1962,117 +1872,16 @@ map_button(KeySym key) const { //////////////////////////////////////////////////////////////////// ButtonHandle x11GraphicsWindow:: map_raw_button(KeyCode key) const { - switch (key) { - case 9: return KeyboardButton::escape(); - case 10: return KeyboardButton::ascii_key('1'); - case 11: return KeyboardButton::ascii_key('2'); - case 12: return KeyboardButton::ascii_key('3'); - case 13: return KeyboardButton::ascii_key('4'); - case 14: return KeyboardButton::ascii_key('5'); - case 15: return KeyboardButton::ascii_key('6'); - case 16: return KeyboardButton::ascii_key('7'); - case 17: return KeyboardButton::ascii_key('8'); - case 18: return KeyboardButton::ascii_key('9'); - case 19: return KeyboardButton::ascii_key('0'); - case 20: return KeyboardButton::ascii_key('-'); - case 21: return KeyboardButton::ascii_key('='); - case 22: return KeyboardButton::backspace(); - case 23: return KeyboardButton::tab(); - case 24: return KeyboardButton::ascii_key('q'); - case 25: return KeyboardButton::ascii_key('w'); - case 26: return KeyboardButton::ascii_key('e'); - case 27: return KeyboardButton::ascii_key('r'); - case 28: return KeyboardButton::ascii_key('t'); - case 29: return KeyboardButton::ascii_key('y'); - case 30: return KeyboardButton::ascii_key('u'); - case 31: return KeyboardButton::ascii_key('i'); - case 32: return KeyboardButton::ascii_key('o'); - case 33: return KeyboardButton::ascii_key('p'); - case 34: return KeyboardButton::ascii_key('['); - case 35: return KeyboardButton::ascii_key(']'); - case 36: return KeyboardButton::enter(); - case 37: return KeyboardButton::lcontrol(); - case 38: return KeyboardButton::ascii_key('a'); - case 39: return KeyboardButton::ascii_key('s'); - case 40: return KeyboardButton::ascii_key('d'); - case 41: return KeyboardButton::ascii_key('f'); - case 42: return KeyboardButton::ascii_key('g'); - case 43: return KeyboardButton::ascii_key('h'); - case 44: return KeyboardButton::ascii_key('j'); - case 45: return KeyboardButton::ascii_key('k'); - case 46: return KeyboardButton::ascii_key('l'); - case 47: return KeyboardButton::ascii_key(';'); - case 48: return KeyboardButton::ascii_key('\''); - case 49: return KeyboardButton::ascii_key('`'); - case 50: return KeyboardButton::lshift(); - case 51: return KeyboardButton::ascii_key('\\'); - case 52: return KeyboardButton::ascii_key('z'); - case 53: return KeyboardButton::ascii_key('x'); - case 54: return KeyboardButton::ascii_key('c'); - case 55: return KeyboardButton::ascii_key('v'); - case 56: return KeyboardButton::ascii_key('b'); - case 57: return KeyboardButton::ascii_key('n'); - case 58: return KeyboardButton::ascii_key('m'); - case 59: return KeyboardButton::ascii_key(','); - case 60: return KeyboardButton::ascii_key('.'); - case 61: return KeyboardButton::ascii_key('/'); - case 62: return KeyboardButton::rshift(); - case 63: return KeyboardButton::ascii_key('*'); - case 64: return KeyboardButton::lalt(); - case 65: return KeyboardButton::space(); - case 66: return KeyboardButton::caps_lock(); - case 67: return KeyboardButton::f1(); - case 68: return KeyboardButton::f2(); - case 69: return KeyboardButton::f3(); - case 70: return KeyboardButton::f4(); - case 71: return KeyboardButton::f5(); - case 72: return KeyboardButton::f6(); - case 73: return KeyboardButton::f7(); - case 74: return KeyboardButton::f8(); - case 75: return KeyboardButton::f9(); - case 76: return KeyboardButton::f10(); - case 77: return KeyboardButton::num_lock(); - case 78: return KeyboardButton::scroll_lock(); - case 79: return KeyboardButton::ascii_key('7'); - case 80: return KeyboardButton::ascii_key('8'); - case 81: return KeyboardButton::ascii_key('9'); - case 82: return KeyboardButton::ascii_key('-'); - case 83: return KeyboardButton::ascii_key('4'); - case 84: return KeyboardButton::ascii_key('5'); - case 85: return KeyboardButton::ascii_key('6'); - case 86: return KeyboardButton::ascii_key('+'); - case 87: return KeyboardButton::ascii_key('1'); - case 88: return KeyboardButton::ascii_key('2'); - case 89: return KeyboardButton::ascii_key('3'); - case 90: return KeyboardButton::ascii_key('0'); - case 91: return KeyboardButton::ascii_key('.'); - - case 95: return KeyboardButton::f11(); - case 96: return KeyboardButton::f12(); - - case 104: return KeyboardButton::enter(); - case 105: return KeyboardButton::rcontrol(); - case 106: return KeyboardButton::ascii_key('/'); - case 107: return KeyboardButton::print_screen(); - case 108: return KeyboardButton::ralt(); - - case 110: return KeyboardButton::home(); - case 111: return KeyboardButton::up(); - case 112: return KeyboardButton::page_up(); - case 113: return KeyboardButton::left(); - case 114: return KeyboardButton::right(); - case 115: return KeyboardButton::end(); - case 116: return KeyboardButton::down(); - case 117: return KeyboardButton::page_down(); - case 118: return KeyboardButton::insert(); - case 119: return KeyboardButton::del(); - - case 127: return KeyboardButton::pause(); - - case 133: return KeyboardButton::lmeta(); - case 134: return KeyboardButton::rmeta(); - case 135: return KeyboardButton::menu(); +#ifdef PHAVE_LINUX_INPUT_H + // Most X11 servers are configured to use the evdev driver, which + // adds 8 to the underlying evdev keycodes (not sure why). + // In any case, this means we can use the same mapping as our raw + // input code, which uses evdev directly. + int index = key - 8; + if (index >= 0) { + return EvdevInputDevice::map_button(index); } +#endif return ButtonHandle::none(); } diff --git a/panda/src/x11display/x11GraphicsWindow.h b/panda/src/x11display/x11GraphicsWindow.h index 5bcb8de0db..d279ecd443 100644 --- a/panda/src/x11display/x11GraphicsWindow.h +++ b/panda/src/x11display/x11GraphicsWindow.h @@ -74,7 +74,6 @@ protected: static Bool check_event(X11_Display *display, XEvent *event, char *arg); void open_raw_mice(); - void poll_raw_mice(); private: X11_Cursor get_cursor(const Filename &filename); @@ -89,6 +88,7 @@ protected: Colormap _colormap; XIC _ic; XVisualInfo *_visual_info; + GraphicsWindowInputDevice *_input; bool _have_xrandr; #ifdef HAVE_XRANDR From 58ddd77d5ed10f5ff87827c67fb5e2c06ad26a2f Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 11 Dec 2015 19:32:45 +0100 Subject: [PATCH 002/424] Support gamepads (and other devices) via evdev interface, query axis+button availability --- panda/src/device/evdevInputDevice.cxx | 50 ++++++++++++++++++++++++--- panda/src/device/inputDevice.cxx | 11 ++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index e03934d658..9b69ead27e 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -25,6 +25,11 @@ #define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7))) +static InputDevice::ControlAxis axis_map[] = { + InputDevice::C_left_x, InputDevice::C_left_y, InputDevice::C_left_trigger, + InputDevice::C_right_x, InputDevice::C_right_y, InputDevice::C_right_trigger +}; + TypeHandle EvdevInputDevice::_type_handle; //////////////////////////////////////////////////////////////////// @@ -95,12 +100,15 @@ do_poll() { //////////////////////////////////////////////////////////////////// bool EvdevInputDevice:: init_device() { - uint8_t evtypes[EV_MAX/8 + 1]; + nassertr(_fd >= 0, false); + + uint8_t evtypes[(EV_CNT + 7) >> 3]; + memset(evtypes, 0, sizeof(evtypes)); char name[256]; char uniq[256]; if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || ioctl(_fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0 || - ioctl(_fd, EVIOCGBIT(0, EV_MAX), &evtypes) < 0) { + ioctl(_fd, EVIOCGBIT(0, sizeof(evtypes)), evtypes) < 0) { close(_fd); _fd = -1; _is_connected = false; @@ -121,6 +129,35 @@ init_device() { _name = ((string)name) + "." + uniq; + if (test_bit(EV_ABS, evtypes)) { + // Check which axes are on the device. + uint8_t axes[(ABS_CNT + 7) >> 3]; + memset(axes, 0, sizeof(axes)); + + int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; + for (int i = 0; i < num_bits; ++i) { + if (test_bit(i, axes)) { + if (i >= 0 && i < 6) { + set_control_map(i, axis_map[i]); + } + } + } + } + + if (test_bit(EV_KEY, evtypes)) { + // Check which buttons are on the device. + uint8_t keys[(KEY_CNT + 7) >> 3]; + memset(keys, 0, sizeof(keys)); + + int num_bits = ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) << 3; + int bi = 0; + for (int i = 0; i < num_bits; ++i) { + if (test_bit(i, keys)) { + set_button_map(bi++, map_button(i)); + } + } + } + if (test_bit(EV_REL, evtypes)) { _flags |= IDF_has_pointer; } @@ -169,6 +206,7 @@ process_events() { int x = _pointer_data.get_x(); int y = _pointer_data.get_y(); + bool have_pointer = false; double time = ClockObject::get_global_clock()->get_frame_time(); ButtonHandle button; @@ -180,11 +218,11 @@ process_events() { case EV_REL: if (events[i].code == REL_X) x += events[i].value; if (events[i].code == REL_Y) y += events[i].value; + have_pointer = true; break; case EV_ABS: - if (events[i].code == ABS_X) x = events[i].value; - if (events[i].code == ABS_Y) y = events[i].value; + set_control_state(events[i].code, events[i].value / 32767.0); break; case EV_KEY: @@ -198,7 +236,9 @@ process_events() { } } - set_pointer(true, x, y, time); + if (have_pointer) { + set_pointer(true, x, y, time); + } return true; } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 25477ca913..a878a7027d 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -239,6 +239,17 @@ set_control_state(int index, double state) { _controls.resize(index + 1, AnalogState()); } + if (device_cat.is_spam()) { + device_cat.spam() + << "Changed control " << index; + + if (_controls[index]._axis != C_none) { + device_cat.spam(false) << " (" << _controls[index]._axis << ")"; + } + + device_cat.spam(false) << " to " << state << "\n"; + } + _controls[index]._state = state; _controls[index]._known = true; } From 35add447e64b674cb2a63323b928a20c7de6ef3d Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 12 Dec 2015 00:55:37 +0100 Subject: [PATCH 003/424] Build on Windows; add XInput gamepad implementation --- dtool/src/parser-inc/windows.h | 1 + panda/src/device/config_device.cxx | 5 + panda/src/device/inputDeviceManager.cxx | 9 +- panda/src/device/p3device_composite2.cxx | 2 +- panda/src/device/xInputDevice.cxx | 214 +++++++++++++++++++++ panda/src/device/xInputDevice.h | 50 +++++ panda/src/event/buttonEventList.h | 2 +- panda/src/windisplay/winGraphicsWindow.cxx | 75 ++++---- panda/src/windisplay/winGraphicsWindow.h | 6 +- 9 files changed, 321 insertions(+), 43 deletions(-) create mode 100644 panda/src/device/xInputDevice.cxx create mode 100644 panda/src/device/xInputDevice.h diff --git a/dtool/src/parser-inc/windows.h b/dtool/src/parser-inc/windows.h index bee8a5c25a..fe3c46e22e 100644 --- a/dtool/src/parser-inc/windows.h +++ b/dtool/src/parser-inc/windows.h @@ -39,6 +39,7 @@ typedef unsigned long ULONG_PTR; // http://msdn.microsoft.com/en-us/library/cc230309.aspx typedef bool BOOL; typedef unsigned long DWORD; +typedef unsigned short WORD; typedef long LONG; typedef long UINT; typedef unsigned long ULONG; diff --git a/panda/src/device/config_device.cxx b/panda/src/device/config_device.cxx index 84e290be91..c3e7613e0d 100644 --- a/panda/src/device/config_device.cxx +++ b/panda/src/device/config_device.cxx @@ -28,6 +28,7 @@ #include "linuxJoystickDevice.h" #include "trackerNode.h" #include "virtualMouse.h" +#include "xInputDevice.h" #include "dconfig.h" @@ -74,4 +75,8 @@ init_libdevice() { EvdevInputDevice::init_type(); LinuxJoystickDevice::init_type(); #endif + +#ifdef _WIN32 + XInputDevice::init_type(); +#endif } diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index d67e21208f..aa799231e3 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -30,9 +30,10 @@ InputDeviceManager *InputDeviceManager::_global_ptr = NULL; // Access: Private // Description: //////////////////////////////////////////////////////////////////// +#ifdef PHAVE_LINUX_INPUT_H InputDeviceManager:: InputDeviceManager() : _inotify_fd(-1) { -#ifdef PHAVE_LINUX_INPUT_H + // Scan /dev/input for a list of input devices. DIR *dir = opendir("/dev/input"); if (dir) { @@ -70,8 +71,12 @@ InputDeviceManager() : _inotify_fd(-1) { device_cat.error() << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; } -#endif } +#else +InputDeviceManager:: +InputDeviceManager() { +} +#endif //////////////////////////////////////////////////////////////////// // Function: InputDeviceManager::Destructor diff --git a/panda/src/device/p3device_composite2.cxx b/panda/src/device/p3device_composite2.cxx index 294e5d69ae..674c1865fe 100644 --- a/panda/src/device/p3device_composite2.cxx +++ b/panda/src/device/p3device_composite2.cxx @@ -6,4 +6,4 @@ #include "trackerData.cxx" #include "trackerNode.cxx" #include "virtualMouse.cxx" - +#include "xInputDevice.cxx" diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx new file mode 100644 index 0000000000..92ab2928c0 --- /dev/null +++ b/panda/src/device/xInputDevice.cxx @@ -0,0 +1,214 @@ +// Filename: xInputDevice.cxx +// Created by: rdb (21Jul15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "xInputDevice.h" + +#ifdef _WIN32 +#include "gamepadButton.h" + +#include + +#ifndef XUSER_MAX_COUNT +#define XUSER_MAX_COUNT 4 +#endif + +#ifndef XINPUT_CAPS_FFB_SUPPORTED +#define XINPUT_CAPS_FFB_SUPPORTED 0x0001 +#endif + +typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); +typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); +//typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); + +static pXInputGetState get_state = NULL; +static pXInputGetCapabilities get_capabilities = NULL; +//static pXInputGetBatteryInformation get_battery_information = NULL; + +bool XInputDevice::_initialized = false; + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::Constructor +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +XInputDevice:: +XInputDevice(DWORD user_index) : + _index(user_index), + _last_packet(-1), + _last_buttons(0) { + + nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT); + + if (!_initialized) { + nassertv(init_xinput()); + } + + if (get_capabilities != NULL) { + XINPUT_CAPABILITIES caps; + if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS) { + _is_connected = true; + } else { + _is_connected = false; + } + + if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { + _device_class = DC_gamepad; + } else { + _device_class = DC_unknown; + } + + if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { + _flags |= IDF_has_vibration; + } + } + +#if 0 + if (get_battery_information != NULL) { + XINPUT_BATTERY_INFORMATION batt; + if (XInputGetBatteryInformation(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) { + if (batt.BatteryType == BATTERY_TYPE_DISCONNECTED) { + _is_connected = false; + + } else if (batt.BatteryType != BATTERY_TYPE_WIRED) { + // This device has a battery. Report the battery level. + _is_connected = true; + _flags |= IDF_has_battery; + _battery_level = batt.BatteryLevel; + _max_battery_level = BATTERY_LEVEL_FULL; + } + } + } +#endif + + _buttons.resize(16); + + // Get the initial state. + XINPUT_STATE state; + if (get_state(_index, &state) != ERROR_SUCCESS) { + // Device was disconnected. + _is_connected = false; + return; + } + + WORD buttons = state.Gamepad.wButtons; + + set_button_map(0, GamepadButton::dpad_up()); + set_button_state(0, (buttons & XINPUT_GAMEPAD_DPAD_UP) != 0); + set_button_map(1, GamepadButton::dpad_down()); + set_button_state(1, (buttons & XINPUT_GAMEPAD_DPAD_DOWN) != 0); + set_button_map(2, GamepadButton::dpad_left()); + set_button_state(2, (buttons & XINPUT_GAMEPAD_DPAD_LEFT) != 0); + set_button_map(3, GamepadButton::dpad_right()); + set_button_state(3, (buttons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0); + set_button_map(4, GamepadButton::start()); + set_button_state(4, (buttons & XINPUT_GAMEPAD_START) != 0); + set_button_map(5, GamepadButton::back()); + set_button_state(5, (buttons & XINPUT_GAMEPAD_BACK) != 0); + set_button_map(6, GamepadButton::lstick()); + set_button_state(6, (buttons & XINPUT_GAMEPAD_LEFT_THUMB) != 0); + set_button_map(7, GamepadButton::rstick()); + set_button_state(7, (buttons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0); + set_button_map(8, GamepadButton::lshoulder()); + set_button_state(8, (buttons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0); + set_button_map(9, GamepadButton::rshoulder()); + set_button_state(9, (buttons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0); + set_button_map(10, GamepadButton::guide()); + set_button_state(10, (buttons & 0x0400) != 0); + set_button_map(11, GamepadButton::action_a()); + set_button_state(11, (buttons & XINPUT_GAMEPAD_A) != 0); + set_button_map(12, GamepadButton::action_b()); + set_button_state(12, (buttons & XINPUT_GAMEPAD_B) != 0); + set_button_map(13, GamepadButton::action_x()); + set_button_state(13, (buttons & XINPUT_GAMEPAD_X) != 0); + set_button_map(14, GamepadButton::action_y()); + set_button_state(14, (buttons & XINPUT_GAMEPAD_Y) != 0); + + _last_buttons = buttons; +} + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::init +// Access: Protected, Static +// Description: Initializes the XInput library. +//////////////////////////////////////////////////////////////////// +bool XInputDevice:: +init_xinput() { + _initialized = true; + HMODULE module = LoadLibraryA("Xinput1_4.dll"); + if (module) { + // Undocumented version (XInputGetStateEx) that includes a + // state bit for the guide button. + get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100)); + if (get_state == NULL) { + get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState"); + if (get_state == NULL) { + device_cat.error() + << "Failed to find function XInputGetState in XInput DLL.\n"; + return false; + } + } + + get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); + return true; + } + + device_cat.error() + << "Failed to load Xinput1_4.dll.\n"; + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::do_poll +// Access: Public, Virtual +// Description: Polls the input device for new activity, to ensure +// it contains the latest events. This will only have +// any effect for some types of input devices; others +// may be updated automatically, and this method will +// be a no-op. +//////////////////////////////////////////////////////////////////// +void XInputDevice:: +do_poll() { + XINPUT_STATE state; + + if (get_state(_index, &state) != ERROR_SUCCESS) { + // Device was disconnected. + _is_connected = false; + return; + } else { + _is_connected = true; + } + + if (state.dwPacketNumber == _last_packet) { + // No change since last time we asked. + return; + } + + WORD changed_buttons = _last_buttons ^ state.Gamepad.wButtons; + + WORD mask = 1; + for (int i = 0; i < 16; ++i) { + if (changed_buttons & mask) { + set_button_state(i, (state.Gamepad.wButtons & mask) != 0); + } + mask <<= 1; + if (i == 10) { + mask <<= 1; + } + } + + _last_buttons = state.Gamepad.wButtons; + _last_packet = state.dwPacketNumber; +} + +#endif // _WIN32 diff --git a/panda/src/device/xInputDevice.h b/panda/src/device/xInputDevice.h new file mode 100644 index 0000000000..7abc0eafc5 --- /dev/null +++ b/panda/src/device/xInputDevice.h @@ -0,0 +1,50 @@ +// Filename: xInputDevice.h +// Created by: rdb (21Jul15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef XINPUTDEVICE_H +#define XINPUTDEVICE_H + +#include "pandabase.h" +#include "inputDevice.h" + +#ifdef _WIN32 + +//////////////////////////////////////////////////////////////////// +// Class : XInputDevice +// Description : This uses Microsoft's XInput library to interface +// with an Xbox 360 game controller. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE XInputDevice : public InputDevice { +PUBLISHED: + XInputDevice(DWORD user_index); + + static bool init_xinput(); + +public: + virtual void do_poll(); + +private: + DWORD _index; + DWORD _last_packet; + WORD _last_buttons; + + static bool _initialized; + + // There are only four of these in existence. + //static XInputDevice _devices[4]; +}; + +#endif // _WIN32 + +#endif diff --git a/panda/src/event/buttonEventList.h b/panda/src/event/buttonEventList.h index ead1a5968c..ca4ae298dc 100644 --- a/panda/src/event/buttonEventList.h +++ b/panda/src/event/buttonEventList.h @@ -35,7 +35,7 @@ class DatagramIterator; // is desired. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_EVENT ButtonEventList : public ParamValueBase { -public: +PUBLISHED: INLINE ButtonEventList(); INLINE ButtonEventList(const ButtonEventList ©); INLINE void operator = (const ButtonEventList ©); diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 4e04206426..9cd90ad3ee 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -146,7 +146,7 @@ move_pointer(int device, int x, int y) { if (device == 0) { // Move the system mouse pointer. if (!_properties.get_foreground() ) - // !_input_devices[0].get_pointer().get_in_window()) + // !_input->get_pointer().get_in_window()) { // If the window doesn't have input focus, or the mouse isn't // currently within the window, forget it. @@ -157,14 +157,14 @@ move_pointer(int device, int x, int y) { get_client_rect_screen(_hWnd, &view_rect); SetCursorPos(view_rect.left + x, view_rect.top + y); - _input_devices[0].set_pointer_in_window(x, y); + _input->set_pointer_in_window(x, y); return true; } else { // Move a raw mouse. if ((device < 1)||(device >= (int)_input_devices.size())) { return false; } - _input_devices[device].set_pointer_in_window(x, y); + //_input_devices[device].set_pointer_in_window(x, y); return true; } } @@ -587,7 +587,6 @@ open_window() { // initializes a parallel array, _input_device_handle, // with the win32 handle of each raw input device. //////////////////////////////////////////////////////////////////// - void WinGraphicsWindow:: initialize_input_devices() { UINT nInputDevices; @@ -597,9 +596,10 @@ initialize_input_devices() { // Clear the handle array, and set up the system keyboard/mouse memset(_input_device_handle, 0, sizeof(_input_device_handle)); - GraphicsWindowInputDevice device = + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); add_input_device(device); + _input = device; // Try initializing the Raw Input function pointers. if (pRegisterRawInputDevices==0) { @@ -656,9 +656,9 @@ initialize_input_devices() { } if (pound2) *pound2 = '.'; _input_device_handle[_input_devices.size()] = pRawInputDeviceList[i].hDevice; - GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_only(this, psName); - device.set_pointer_in_window(0,0); - add_input_device(device); + //GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_only(this, psName); + //device.set_pointer_in_window(0,0); + //add_input_device(device); } } } @@ -1578,8 +1578,8 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } SetCapture(hwnd); - _input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); - _input_devices[0].button_down(MouseButton::button(0), get_message_time()); + _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); + _input->button_down(MouseButton::button(0), get_message_time()); // A button-click in the window means to grab the keyboard focus. set_focus(); @@ -1590,8 +1590,8 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } SetCapture(hwnd); - _input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); - _input_devices[0].button_down(MouseButton::button(1), get_message_time()); + _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); + _input->button_down(MouseButton::button(1), get_message_time()); // A button-click in the window means to grab the keyboard focus. set_focus(); return 0; @@ -1601,8 +1601,8 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } SetCapture(hwnd); - _input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); - _input_devices[0].button_down(MouseButton::button(2), get_message_time()); + _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); + _input->button_down(MouseButton::button(2), get_message_time()); // A button-click in the window means to grab the keyboard focus. set_focus(); return 0; @@ -1614,11 +1614,11 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { } SetCapture(hwnd); int whichButton = GET_XBUTTON_WPARAM(wparam); - _input_devices[0].set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); + _input->set_pointer_in_window(translate_mouse(LOWORD(lparam)), translate_mouse(HIWORD(lparam))); if (whichButton == XBUTTON1) { - _input_devices[0].button_down(MouseButton::button(3), get_message_time()); + _input->button_down(MouseButton::button(3), get_message_time()); } else if (whichButton == XBUTTON2) { - _input_devices[0].button_down(MouseButton::button(4), get_message_time()); + _input->button_down(MouseButton::button(4), get_message_time()); } } return 0; @@ -1628,7 +1628,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } ReleaseCapture(); - _input_devices[0].button_up(MouseButton::button(0), get_message_time()); + _input->button_up(MouseButton::button(0), get_message_time()); return 0; case WM_MBUTTONUP: @@ -1636,7 +1636,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } ReleaseCapture(); - _input_devices[0].button_up(MouseButton::button(1), get_message_time()); + _input->button_up(MouseButton::button(1), get_message_time()); return 0; case WM_RBUTTONUP: @@ -1644,7 +1644,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { resend_lost_keypresses(); } ReleaseCapture(); - _input_devices[0].button_up(MouseButton::button(2), get_message_time()); + _input->button_up(MouseButton::button(2), get_message_time()); return 0; case WM_XBUTTONUP: @@ -1655,9 +1655,9 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { ReleaseCapture(); int whichButton = GET_XBUTTON_WPARAM(wparam); if (whichButton == XBUTTON1) { - _input_devices[0].button_up(MouseButton::button(3), get_message_time()); + _input->button_up(MouseButton::button(3), get_message_time()); } else if (whichButton == XBUTTON2) { - _input_devices[0].button_up(MouseButton::button(4), get_message_time()); + _input->button_up(MouseButton::button(4), get_message_time()); } } return 0; @@ -1777,7 +1777,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (ime_aware) { wstring ws; - _input_devices[0].candidate(ws, 0, 0, 0); + _input->candidate(ws, 0, 0, 0); } break; @@ -1808,7 +1808,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { ime_buffer, ime_buffer_size_bytes); size_t num_chars = result_size / sizeof(wchar_t); for (size_t i = 0; i < num_chars; ++i) { - _input_devices[0].keystroke(ime_buffer[i]); + _input->keystroke(ime_buffer[i]); } } @@ -1821,7 +1821,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { result_size = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, ime_buffer, ime_buffer_size); size_t num_chars = result_size / sizeof(wchar_t); - _input_devices[0].candidate(wstring(ime_buffer, num_chars), + _input->candidate(wstring(ime_buffer, num_chars), min(cursor_pos, delta_start), max(cursor_pos, delta_start), cursor_pos); @@ -1843,7 +1843,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // poorly defined. Now we are using RegisterClassW etc., which // means WM_CHAR is absolutely supposed to be utf-16. if (!_ime_open) { - _input_devices[0].keystroke(wparam); + _input->keystroke(wparam); } break; @@ -1975,7 +1975,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (lptstr != NULL) { char *pChar; for (pChar=lptstr; *pChar!=NULL; pChar++) { - _input_devices[0].keystroke((uchar)*pChar); + _input->keystroke((uchar)*pChar); } GlobalUnlock(hglb); } @@ -2098,7 +2098,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { << "killfocus\n"; } - _input_devices[0].focus_lost(get_message_time()); + _input->focus_lost(get_message_time()); properties.set_foreground(false); system_changed_properties(properties); break; @@ -2409,9 +2409,9 @@ show_error_message(DWORD message_id) { //////////////////////////////////////////////////////////////////// void WinGraphicsWindow:: handle_keypress(ButtonHandle key, int x, int y, double time) { - _input_devices[0].set_pointer_in_window(x, y); + _input->set_pointer_in_window(x, y); if (key != ButtonHandle::none()) { - _input_devices[0].button_down(key, time); + _input->button_down(key, time); } } @@ -2425,7 +2425,7 @@ handle_keypress(ButtonHandle key, int x, int y, double time) { void WinGraphicsWindow:: handle_keyresume(ButtonHandle key, double time) { if (key != ButtonHandle::none()) { - _input_devices[0].button_resume_down(key, time); + _input->button_resume_down(key, time); } } @@ -2437,7 +2437,7 @@ handle_keyresume(ButtonHandle key, double time) { void WinGraphicsWindow:: handle_keyrelease(ButtonHandle key, double time) { if (key != ButtonHandle::none()) { - _input_devices[0].button_up(key, time); + _input->button_up(key, time); } } @@ -2449,7 +2449,7 @@ handle_keyrelease(ButtonHandle key, double time) { void WinGraphicsWindow:: handle_raw_keypress(ButtonHandle key, double time) { if (key != ButtonHandle::none()) { - _input_devices[0].raw_button_down(key, time); + _input->raw_button_down(key, time); } } @@ -2461,7 +2461,7 @@ handle_raw_keypress(ButtonHandle key, double time) { void WinGraphicsWindow:: handle_raw_keyrelease(ButtonHandle key, double time) { if (key != ButtonHandle::none()) { - _input_devices[0].raw_button_up(key, time); + _input->raw_button_up(key, time); } } @@ -2777,7 +2777,7 @@ handle_raw_input(HRAWINPUT hraw) { if (raw->header.hDevice == 0) { return; } - +/* for (int i = 1; i < (int)(_input_devices.size()); ++i) { if (_input_device_handle[i] == raw->header.hDevice) { int adjx = raw->data.mouse.lLastX; @@ -2823,6 +2823,7 @@ handle_raw_input(HRAWINPUT hraw) { } } } +*/ } //////////////////////////////////////////////////////////////////// @@ -2832,7 +2833,7 @@ handle_raw_input(HRAWINPUT hraw) { //////////////////////////////////////////////////////////////////// bool WinGraphicsWindow:: handle_mouse_motion(int x, int y) { - _input_devices[0].set_pointer_in_window(x, y); + _input->set_pointer_in_window(x, y); return false; } @@ -2844,7 +2845,7 @@ handle_mouse_motion(int x, int y) { void WinGraphicsWindow:: handle_mouse_exit() { // note: 'mouse_motion' is considered the 'entry' event - _input_devices[0].set_pointer_out_of_window(); + _input->set_pointer_out_of_window(); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/windisplay/winGraphicsWindow.h b/panda/src/windisplay/winGraphicsWindow.h index 1b3ee93f95..91350ee3af 100644 --- a/panda/src/windisplay/winGraphicsWindow.h +++ b/panda/src/windisplay/winGraphicsWindow.h @@ -110,7 +110,7 @@ protected: virtual DWORD make_style(bool fullscreen); - virtual void reconsider_fullscreen_size(DWORD &x_size, DWORD &y_size, + virtual void reconsider_fullscreen_size(DWORD &x_size, DWORD &y_size, DWORD &bitdepth); virtual void support_overlay_window(bool flag); @@ -179,6 +179,8 @@ private: bool _lalt_down; bool _ralt_down; + GraphicsWindowInputDevice *_input; + // following adds support platform specfic window processing // functions. typedef pset WinProcClasses; @@ -276,7 +278,7 @@ private: return get_class_type(); } virtual TypeHandle force_init_type() {init_type(); return get_class_type();} - + private: static TypeHandle _type_handle; }; From dd8344455cc426faa9e44206d8bfd20134b3c023 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 12 Dec 2015 22:07:43 +0100 Subject: [PATCH 004/424] XInput gamepad control states, battery information, HID properties --- dtool/src/parser-inc/windows.h | 1 + makepanda/makepanda.py | 5 +- panda/src/device/inputDevice.I | 2 + panda/src/device/inputDevice.cxx | 18 ++-- panda/src/device/inputDevice.h | 4 + panda/src/device/inputDeviceManager.cxx | 6 +- panda/src/device/xInputDevice.cxx | 115 ++++++++++++++++++++++-- 7 files changed, 127 insertions(+), 24 deletions(-) diff --git a/dtool/src/parser-inc/windows.h b/dtool/src/parser-inc/windows.h index fe3c46e22e..4a36e5b2d2 100644 --- a/dtool/src/parser-inc/windows.h +++ b/dtool/src/parser-inc/windows.h @@ -42,6 +42,7 @@ typedef unsigned long DWORD; typedef unsigned short WORD; typedef long LONG; typedef long UINT; +typedef unsigned char BYTE; typedef unsigned long ULONG; typedef long long LONGLONG; typedef long HRESULT; diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index d11d0825bd..07d8950606 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -578,6 +578,7 @@ if (COMPILER == "MSVC"): LibName("WINGDI", "gdi32.lib") LibName("ADVAPI", "advapi32.lib") LibName("IPHLPAPI", "iphlpapi.lib") + LibName("SETUPAPI", "setupapi.lib") LibName("GL", "opengl32.lib") LibName("GLES", "libgles_cm.lib") LibName("GLES2", "libGLESv2.lib") @@ -3849,8 +3850,8 @@ if (not RUNTIME): # if (not RUNTIME): - OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', - 'TIFF', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', + OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'TIFF', + 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2', 'SETUPAPI', 'SQUISH', 'NVIDIACG', 'VORBIS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI'] TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx') diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 99962b7750..8f04226896 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -21,6 +21,8 @@ INLINE InputDevice:: InputDevice() : _flags(0), _device_class(DC_unknown), + _vendor_id(0), + _product_id(0), _is_connected(false), _event_sequence(0), _enable_pointer_events(false), diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index a878a7027d..4637673672 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -32,6 +32,8 @@ InputDevice(const string &name, DeviceClass dev_class, int flags) : _name(name), _flags(flags), _device_class(dev_class), + _vendor_id(0), + _product_id(0), _is_connected(true), _event_sequence(0), _enable_pointer_events(false), @@ -48,7 +50,7 @@ InputDevice(const string &name, DeviceClass dev_class, int flags) : //////////////////////////////////////////////////////////////////// InputDevice:: InputDevice(const InputDevice ©) { - *this = copy; + nassertv(false); } //////////////////////////////////////////////////////////////////// @@ -58,16 +60,7 @@ InputDevice(const InputDevice ©) { //////////////////////////////////////////////////////////////////// void InputDevice:: operator = (const InputDevice ©) { - LightMutexHolder holder(_lock); - LightMutexHolder holder1(copy._lock); - _name = copy._name; - _flags = copy._flags; - _is_connected = copy._is_connected; - _event_sequence = copy._event_sequence; - _enable_pointer_events = copy._enable_pointer_events; - _pointer_data = copy._pointer_data; - _button_events = copy._button_events; - _pointer_events = copy._pointer_events; + nassertv(false); } //////////////////////////////////////////////////////////////////// @@ -315,10 +308,9 @@ output(ostream &out) const { if (_battery_level > 0 && _max_battery_level > 0) { out << " ["; short i = 0; - for (; i < _battery_level - 1; ++i) { + for (; i < _battery_level; ++i) { out << '='; } - out << '/'; for (; i < _max_battery_level; ++i) { out << ' '; } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 407e6cdb77..758ea366f8 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -206,9 +206,13 @@ protected: LightMutex _lock; string _name; + string _serial_number; + string _manufacturer; DeviceClass _device_class; int _flags; int _event_sequence; + short _vendor_id; + short _product_id; bool _is_connected; bool _enable_pointer_events; PointerData _pointer_data; diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index aa799231e3..90f1e1921f 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -143,7 +143,7 @@ add_device(InputDevice *device) { LightMutexHolder holder(_lock); _all_devices.push_back(device); } - throw_event("device-added", device); + throw_event("connect-device", device); } //////////////////////////////////////////////////////////////////// @@ -163,7 +163,7 @@ remove_device(InputDevice *device) { _all_devices.erase(it); } - throw_event("device-removed", device); + throw_event("disconnect-device", device); } //////////////////////////////////////////////////////////////////// @@ -220,7 +220,7 @@ poll() { device_cat.info() << "Removed input device " << *device << "\n"; } - throw_event("device-removed", device.p()); + throw_event("disconnect-device", device.p()); } } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 92ab2928c0..13a2ffdc19 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -18,6 +18,7 @@ #include "gamepadButton.h" #include +#include #ifndef XUSER_MAX_COUNT #define XUSER_MAX_COUNT 4 @@ -27,13 +28,47 @@ #define XINPUT_CAPS_FFB_SUPPORTED 0x0001 #endif +#ifndef BATTERY_DEVTYPE_GAMEPAD +#define BATTERY_DEVTYPE_GAMEPAD 0x00 +#endif + +#ifndef BATTERY_TYPE_DISCONNECTED +#define BATTERY_TYPE_DISCONNECTED 0x00 +#endif + +#ifndef BATTERY_TYPE_WIRED +#define BATTERY_TYPE_WIRED 0x01 +#endif + +#ifndef BATTERY_LEVEL_FULL +#define BATTERY_LEVEL_FULL 0x03 +#endif + +typedef struct _XINPUT_BATTERY_INFORMATION { + BYTE BatteryType; + BYTE BatteryLevel; +} XINPUT_BATTERY_INFORMATION; + +// Undocumented, I figured out how this looks by trial and error. +struct XINPUT_BUSINFO { + WORD VendorID; + WORD ProductID; + WORD RevisionID; + WORD Unknown1; // Unknown - padding? + DWORD InstanceID; + DWORD Unknown2; + //WORD Unknown3; +}; + typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); -//typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); +typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); +typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *); static pXInputGetState get_state = NULL; static pXInputGetCapabilities get_capabilities = NULL; -//static pXInputGetBatteryInformation get_battery_information = NULL; +static pXInputGetBatteryInformation get_battery_information = NULL; +static pXInputGetBaseBusInformation get_base_bus_information = NULL; bool XInputDevice::_initialized = false; @@ -73,10 +108,9 @@ XInputDevice(DWORD user_index) : } } -#if 0 if (get_battery_information != NULL) { XINPUT_BATTERY_INFORMATION batt; - if (XInputGetBatteryInformation(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) { + if (get_battery_information(_index, 0, &batt) == ERROR_SUCCESS) { if (batt.BatteryType == BATTERY_TYPE_DISCONNECTED) { _is_connected = false; @@ -89,8 +123,52 @@ XInputDevice(DWORD user_index) : } } } -#endif + // Get information about the USB device. + // This is not documented at all. I'm probably the first to try this. + XINPUT_BUSINFO businfo; + if (get_base_bus_information != NULL && + get_base_bus_information(0, &businfo) == ERROR_SUCCESS) { + _vendor_id = businfo.VendorID; + _product_id = businfo.ProductID; + + { + // Reformat the serial number into its original hex string form. + char sn[10]; + sprintf(sn, "%08X", businfo.InstanceID); + _serial_number.assign(sn, 8); + } + + // Get information about the device from Windows. For that, we'll + // first need to construct the device path. Fortunately, we now have + // enough information to do so. + char path[32]; + sprintf(path, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID); + + DEVINST inst; + if (CM_Locate_DevNodeA(&inst, path, 0) != 0) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Could not locate device node " << path << "\n"; + } + } else { + // Get the device properties we need. + char buffer[4096]; + ULONG buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) { + _name.assign(buffer); + } + buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) { + _manufacturer.assign(buffer); + } + } + } else { + // We need something to name it. + _name = "XInput Device"; + } + + _controls.resize(6); _buttons.resize(16); // Get the initial state. @@ -103,6 +181,19 @@ XInputDevice(DWORD user_index) : WORD buttons = state.Gamepad.wButtons; + set_control_map(0, C_left_trigger); + set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); + set_control_map(1, C_right_trigger); + set_control_state(1, state.Gamepad.bRightTrigger / 255.0); + set_control_map(2, C_left_x); + set_control_state(2, state.Gamepad.sThumbLX / 32767.0); + set_control_map(3, C_left_y); + set_control_state(3, state.Gamepad.sThumbLY / 32767.0); + set_control_map(4, C_right_x); + set_control_state(4, state.Gamepad.sThumbRX / 32767.0); + set_control_map(5, C_right_y); + set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + set_button_map(0, GamepadButton::dpad_up()); set_button_state(0, (buttons & XINPUT_GAMEPAD_DPAD_UP) != 0); set_button_map(1, GamepadButton::dpad_down()); @@ -146,6 +237,9 @@ bool XInputDevice:: init_xinput() { _initialized = true; HMODULE module = LoadLibraryA("Xinput1_4.dll"); + if (!module) { + module = LoadLibraryA("Xinput1_3.dll"); + } if (module) { // Undocumented version (XInputGetStateEx) that includes a // state bit for the guide button. @@ -160,11 +254,13 @@ init_xinput() { } get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); + get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation"); + get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104)); return true; } device_cat.error() - << "Failed to load Xinput1_4.dll.\n"; + << "Failed to load XInput DLL.\n"; return false; } @@ -207,6 +303,13 @@ do_poll() { } } + set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); + set_control_state(1, state.Gamepad.bRightTrigger / 255.0); + set_control_state(2, state.Gamepad.sThumbLX / 32767.0); + set_control_state(3, state.Gamepad.sThumbLY / 32767.0); + set_control_state(4, state.Gamepad.sThumbRX / 32767.0); + set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + _last_buttons = state.Gamepad.wButtons; _last_packet = state.dwPacketNumber; } From c256faccd6de49571bb92e5ccaaab27f5b9aae27 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 Dec 2015 11:34:33 +0100 Subject: [PATCH 005/424] Forgot to add parser-inc file for XInput --- dtool/src/parser-inc/XInput.h | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 dtool/src/parser-inc/XInput.h diff --git a/dtool/src/parser-inc/XInput.h b/dtool/src/parser-inc/XInput.h new file mode 100644 index 0000000000..f770cf3d71 --- /dev/null +++ b/dtool/src/parser-inc/XInput.h @@ -0,0 +1,2 @@ +struct XINPUT_STATE; +struct XINPUT_CAPABILITIES; From d20d6169183df18e941300cebf836008935486ad Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 16 Dec 2015 18:30:26 +0100 Subject: [PATCH 006/424] XInput gamepad hotplugging, add base.devices.gamepads --- direct/src/showbase/ShowBase.py | 7 + panda/src/device/inputDevice.h | 1 + panda/src/device/inputDeviceManager.cxx | 103 ++++++-- panda/src/device/inputDeviceManager.h | 26 +- panda/src/device/inputDeviceSet.I | 44 ++++ panda/src/device/inputDeviceSet.cxx | 170 +++++++++++++ panda/src/device/inputDeviceSet.h | 67 ++++++ panda/src/device/p3device_composite1.cxx | 1 + panda/src/device/xInputDevice.cxx | 289 ++++++++++++++--------- panda/src/device/xInputDevice.h | 18 +- 10 files changed, 592 insertions(+), 134 deletions(-) create mode 100755 panda/src/device/inputDeviceSet.I create mode 100755 panda/src/device/inputDeviceSet.cxx create mode 100755 panda/src/device/inputDeviceSet.h diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 89168d57d6..621fb75b6b 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -295,6 +295,10 @@ class ShowBase(DirectObject.DirectObject): self.physicsMgrEnabled = 0 self.physicsMgrAngular = 0 + ## This is the global input device manager, which keeps track of + ## connected input devices. + self.devices = InputDeviceManager.getGlobalPtr() + self.createStats() self.AppHasAudioFocus = 1 @@ -1823,6 +1827,9 @@ class ShowBase(DirectObject.DirectObject): return Task.cont def __dataLoop(self, state): + # Check if there were newly connected devices. + self.devices.update() + # traverse the data graph. This reads all the control # inputs (from the mouse and keyboard, for instance) and also # directly acts upon them (for instance, to move the avatar). diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 758ea366f8..a6aaa5a015 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -62,6 +62,7 @@ PUBLISHED: // This enum contains information that can be used to identify the // type of input device. enum DeviceClass { + // It is not known what type of device this is. DC_unknown, // This means that the device doesn't correspond to a physical diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 90f1e1921f..44b038adda 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -23,6 +23,12 @@ #include #endif +static ConfigVariableDouble xinput_detection_delay +("xinput-detection-delay", 0.5, + PRC_DESC("How many seconds to wait between each check to see whether " + "an XInput has been connected. This check is not done every " + "frame in order to prevent slowdown.")); + InputDeviceManager *InputDeviceManager::_global_ptr = NULL; //////////////////////////////////////////////////////////////////// @@ -72,6 +78,34 @@ InputDeviceManager() : _inotify_fd(-1) { << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; } } +#elif defined(_WIN32) +InputDeviceManager:: +InputDeviceManager() : + _xinput_device0(0), + _xinput_device1(1), + _xinput_device2(2), + _xinput_device3(3), + _last_detection(0.0) { + + // These devices are bound to the lifetime of the input manager. + _xinput_device0.local_object(); + _xinput_device1.local_object(); + _xinput_device2.local_object(); + _xinput_device3.local_object(); + + if (_xinput_device0.is_connected()) { + _connected_devices.add_device(&_xinput_device0); + } + if (_xinput_device1.is_connected()) { + _connected_devices.add_device(&_xinput_device1); + } + if (_xinput_device2.is_connected()) { + _connected_devices.add_device(&_xinput_device2); + } + if (_xinput_device3.is_connected()) { + _connected_devices.add_device(&_xinput_device3); + } +} #else InputDeviceManager:: InputDeviceManager() { @@ -124,7 +158,7 @@ consider_add_linux_device(const string &name) { } _devices_by_path[name] = device; - _all_devices.push_back(MOVE(device)); + _connected_devices.add_device(MOVE(device)); return true; } @@ -133,46 +167,67 @@ consider_add_linux_device(const string &name) { #endif //////////////////////////////////////////////////////////////////// -// Function: InputDeviceManager::add_device +// Function: InputDeviceManager::get_gamepads // Access: Public -// Description: Called when a new device has been discovered. +// Description: Returns all currently connected gamepad devices. +//////////////////////////////////////////////////////////////////// +InputDeviceSet InputDeviceManager:: +get_gamepads() const { + InputDeviceSet gamepads; + LightMutexHolder holder(_lock); + + for (size_t i = 0; i < _connected_devices.size(); ++i) { + InputDevice *device = _connected_devices[i]; + if (device->get_device_class() == InputDevice::DC_gamepad) { + gamepads.add_device(device); + } + } + + return gamepads; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::add_device +// Access: Published +// Description: Called when a new device has been discovered. This +// may also be used to register virtual devices. +// +// This sends a connect-device event. //////////////////////////////////////////////////////////////////// void InputDeviceManager:: add_device(InputDevice *device) { { LightMutexHolder holder(_lock); - _all_devices.push_back(device); + _connected_devices.add_device(device); } throw_event("connect-device", device); } //////////////////////////////////////////////////////////////////// // Function: InputDeviceManager::remove_device -// Access: Public -// Description: Called when a device has been removed. +// Access: Published +// Description: Called when a device has been removed, or when a +// device should otherwise no longer be tracked. +// +// This sends a disconnect-device event. //////////////////////////////////////////////////////////////////// void InputDeviceManager:: remove_device(InputDevice *device) { { LightMutexHolder holder(_lock); - - InputDevices::iterator it; - it = std::find(_all_devices.begin(), _all_devices.end(), device); - nassertv_always(it != _all_devices.end()); - - _all_devices.erase(it); + _connected_devices.remove_device(device); } throw_event("disconnect-device", device); } //////////////////////////////////////////////////////////////////// -// Function: InputDeviceManager::poll +// Function: InputDeviceManager::update // Access: Public // Description: Polls the system to see if there are any new devices. //////////////////////////////////////////////////////////////////// void InputDeviceManager:: -poll() { +update() { #ifdef PHAVE_LINUX_INPUT_H // We use inotify to tell us whether a device was added, removed, // or has changed permissions to allow us to access it. @@ -213,8 +268,8 @@ poll() { InputDevices::iterator it2; _devices_by_path.erase(it); - it2 = std::find(_all_devices.begin(), _all_devices.end(), device); - _all_devices.erase(it2); + it2 = std::find(_connected_devices.begin(), _connected_devices.end(), device); + _connected_devices.erase(it2); if (device_cat.is_info()) { device_cat.info() @@ -242,4 +297,20 @@ poll() { ptr += sizeof(inotify_event) + event->len; } #endif + +#ifdef _WIN32 + // XInput doesn't provide a very good hot-plugging interface. We just + // check if it's connected every so often. Perhaps we can switch to + // using RegisterDeviceNotification in the future. + double time_now = ClockObject::get_global_clock()->get_real_time(); + if (time_now - _last_detection > xinput_detection_delay.get_value()) { + // I've heard this can be quite slow if no device is detected. We + // should probably move it to a thread. + _xinput_device0.detect(this); + _xinput_device1.detect(this); + _xinput_device2.detect(this); + _xinput_device3.detect(this); + _last_detection = time_now; + } +#endif } diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 3c05cf96fd..3f2c2cea43 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -18,6 +18,11 @@ #include "pandabase.h" #include "lightMutex.h" #include "inputDevice.h" +#include "inputDeviceSet.h" + +#ifdef _WIN32 +#include "xinputDevice.h" +#endif //////////////////////////////////////////////////////////////////// // Class : InputDeviceManager @@ -35,14 +40,19 @@ private: #endif public: + InputDeviceSet get_gamepads() const; + +PUBLISHED: void add_device(InputDevice *device); void remove_device(InputDevice *device); -PUBLISHED: - void poll(); + void update(); INLINE static InputDeviceManager *get_global_ptr(); + // The set of all currently connected gamepad devices. + MAKE_PROPERTY(gamepads, get_gamepads); + private: LightMutex _lock; @@ -53,8 +63,16 @@ private: DevicesByPath _devices_by_path; #endif - typedef pvector InputDevices; - InputDevices _all_devices; +#ifdef _WIN32 + // There are always exactly four of these in existence. + XInputDevice _xinput_device0; + XInputDevice _xinput_device1; + XInputDevice _xinput_device2; + XInputDevice _xinput_device3; + double _last_detection; +#endif + + InputDeviceSet _connected_devices; static InputDeviceManager *_global_ptr; }; diff --git a/panda/src/device/inputDeviceSet.I b/panda/src/device/inputDeviceSet.I new file mode 100755 index 0000000000..ea12582570 --- /dev/null +++ b/panda/src/device/inputDeviceSet.I @@ -0,0 +1,44 @@ +// Filename: inputDeviceSet.I +// Created by: rdb (16Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::Destructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +INLINE InputDeviceSet:: +~InputDeviceSet() { +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::operator [] +// Access: Published +// Description: Returns the nth InputDevice in the collection. +//////////////////////////////////////////////////////////////////// +INLINE InputDevice *InputDeviceSet:: +operator [] (size_t index) const { + nassertr(index < _devices.size(), NULL); + return _devices[index]; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::size +// Access: Published +// Description: Returns the number of devices in the collection. +//////////////////////////////////////////////////////////////////// +INLINE size_t InputDeviceSet:: +size() const { + return _devices.size(); +} diff --git a/panda/src/device/inputDeviceSet.cxx b/panda/src/device/inputDeviceSet.cxx new file mode 100755 index 0000000000..dec094a957 --- /dev/null +++ b/panda/src/device/inputDeviceSet.cxx @@ -0,0 +1,170 @@ +// Filename: inputDeviceSet.cxx +// Created by: rdb (16Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#include "inputDeviceSet.h" +#include "indent.h" + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +InputDeviceSet:: +InputDeviceSet() { +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::Copy Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +InputDeviceSet:: +InputDeviceSet(const InputDeviceSet ©) : + _devices(copy._devices) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::Copy Assignment Operator +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +operator = (const InputDeviceSet ©) { + _devices = copy._devices; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::add_device +// Access: Public +// Description: Adds a new InputDevice to the collection. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +add_device(InputDevice *device) { + _devices.insert(device); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::remove_device +// Access: Public +// Description: Removes the indicated InputDevice from the collection. +// Returns true if the device was removed, false if it was +// not a member of the collection. +//////////////////////////////////////////////////////////////////// +bool InputDeviceSet:: +remove_device(InputDevice *device) { + return _devices.erase(device) > 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::add_devices_from +// Access: Public +// Description: Adds all the InputDevices indicated in the other +// collection to this device. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +add_devices_from(const InputDeviceSet &other) { + size_t other_num_devices = other.size(); + for (size_t i = 0; i < other_num_devices; i++) { + _devices.push_back(other[i]); + } + _devices.sort(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::remove_devices_from +// Access: Public +// Description: Removes from this collection all of the InputDevices +// listed in the other collection. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +remove_devices_from(const InputDeviceSet &other) { + InputDevices new_devices; + InputDevices::const_iterator it; + for (it = _devices.begin(); it != _devices.end(); ++it) { + if (!other.has_device(*it)) { + new_devices.push_back(*it); + } + } + new_devices.sort(); + _devices.swap(new_devices); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::has_device +// Access: Public +// Description: Returns true if the indicated InputDevice appears in +// this collection, false otherwise. +//////////////////////////////////////////////////////////////////// +bool InputDeviceSet:: +has_device(InputDevice *device) const { + InputDevices::const_iterator it = _devices.find(device); + return (it != _devices.end()); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::clear +// Access: Published +// Description: Removes all InputDevices from the collection. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +clear() { + _devices.clear(); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::reserve +// Access: Published +// Description: This is a hint to Panda to allocate enough memory +// to hold the given number of NodePaths, if you know +// ahead of time how many you will be adding. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +reserve(size_t num) { + _devices.reserve(num); +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::output +// Access: Published +// Description: Writes a brief one-line description of the +// InputDeviceSet to the indicated output stream. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +output(ostream &out) const { + if (_devices.size() == 1) { + out << "1 input device"; + } else { + out << _devices.size() << " input devices"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceSet::write +// Access: Published +// Description: Writes a complete multi-line description of the +// InputDeviceSet to the indicated output stream. +//////////////////////////////////////////////////////////////////// +void InputDeviceSet:: +write(ostream &out, int indent_level) const { + output(indent(out, indent_level)); + out << ":\n"; + indent_level += 2; + + InputDevices::const_iterator it; + for (it = _devices.begin(); it != _devices.end(); ++it) { + (*it)->output(indent(out, indent_level)); + out << '\n'; + } +} diff --git a/panda/src/device/inputDeviceSet.h b/panda/src/device/inputDeviceSet.h new file mode 100755 index 0000000000..be9196752a --- /dev/null +++ b/panda/src/device/inputDeviceSet.h @@ -0,0 +1,67 @@ +// Filename: inputDeviceSet.h +// Created by: rdb (16Dec15) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) Carnegie Mellon University. All rights reserved. +// +// All use of this software is subject to the terms of the revised BSD +// license. You should have received a copy of this license along +// with this source code in a file named "LICENSE." +// +//////////////////////////////////////////////////////////////////// + +#ifndef INPUTDEVICESET_H +#define INPUTDEVICESET_H + +#include "pandabase.h" +#include "ordered_vector.h" +#include "inputDevice.h" + +//////////////////////////////////////////////////////////////////// +// Class : InputDeviceSet +// Description : Manages a list of InputDevice objects, as returned +// by various InputDeviceManager methods. This is +// implemented like a set, meaning the same device +// cannot occur more than once. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_DEVICE InputDeviceSet { +PUBLISHED: + InputDeviceSet(); + InputDeviceSet(const InputDeviceSet ©); + void operator = (const InputDeviceSet ©); + INLINE ~InputDeviceSet(); + +public: + void add_device(InputDevice *device); + bool remove_device(InputDevice *device); + void add_devices_from(const InputDeviceSet &other); + void remove_devices_from(const InputDeviceSet &other); + bool has_device(InputDevice *device) const; + +PUBLISHED: + void clear(); + void reserve(size_t num); + + INLINE InputDevice *operator [] (size_t index) const; + INLINE size_t size() const; + + void output(ostream &out) const; + void write(ostream &out, int indent_level = 0) const; + +private: + // This is currently implemented as ov_set instead of a regular set + // so that we can still support random access. + typedef ov_set InputDevices; + InputDevices _devices; +}; + +INLINE ostream &operator << (ostream &out, const InputDeviceSet &col) { + col.output(out); + return out; +} + +#include "inputDeviceSet.I" + +#endif diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 80fa832775..2e541a6f47 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -8,4 +8,5 @@ #include "evdevInputDevice.cxx" #include "inputDevice.cxx" #include "inputDeviceManager.cxx" +#include "inputDeviceSet.cxx" #include "linuxJoystickDevice.cxx" diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 13a2ffdc19..459d5703d1 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -16,6 +16,8 @@ #ifdef _WIN32 #include "gamepadButton.h" +#include "inputDeviceManager.h" +#include "string_utils.h" #include #include @@ -89,34 +91,166 @@ XInputDevice(DWORD user_index) : nassertv(init_xinput()); } - if (get_capabilities != NULL) { - XINPUT_CAPABILITIES caps; - if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS) { - _is_connected = true; - } else { - _is_connected = false; + _name = "XInput Device #"; + _name += format_string(user_index); + + _controls.resize(6); + _buttons.resize(16); + + set_control_map(0, C_left_trigger); + set_control_map(1, C_right_trigger); + set_control_map(2, C_left_x); + set_control_map(3, C_left_y); + set_control_map(4, C_right_x); + set_control_map(5, C_right_y); + + set_button_map(0, GamepadButton::dpad_up()); + set_button_map(1, GamepadButton::dpad_down()); + set_button_map(2, GamepadButton::dpad_left()); + set_button_map(3, GamepadButton::dpad_right()); + set_button_map(4, GamepadButton::start()); + set_button_map(5, GamepadButton::back()); + set_button_map(6, GamepadButton::lstick()); + set_button_map(7, GamepadButton::rstick()); + set_button_map(8, GamepadButton::lshoulder()); + set_button_map(9, GamepadButton::rshoulder()); + set_button_map(10, GamepadButton::guide()); + set_button_map(11, GamepadButton::action_a()); + set_button_map(12, GamepadButton::action_b()); + set_button_map(13, GamepadButton::action_x()); + set_button_map(14, GamepadButton::action_y()); + + // Check if the device is connected. If so, initialize it. + XINPUT_CAPABILITIES caps; + XINPUT_STATE state; + if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS && + get_state(_index, &state) == ERROR_SUCCESS) { + _is_connected = true; + init_device(caps, state); + } else { + _is_connected = false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::detect +// Access: Public +// Description: Called periodically by the InputDeviceManager to +// detect whether the device is currently connected. +//////////////////////////////////////////////////////////////////// +void XInputDevice:: +detect(InputDeviceManager *mgr) { + bool connected = false; + + XINPUT_CAPABILITIES caps; + XINPUT_STATE state; + if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS && + get_state(_index, &state) == ERROR_SUCCESS) { + connected = true; + } else { + connected = false; + } + + LightMutexHolder holder(_lock); + if (connected == _is_connected) { + // Nothing changed. + return; + } + _is_connected = connected; + + if (connected) { + init_device(caps, state); + mgr->add_device(this); + } else { + mgr->remove_device(this); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::init_xinput +// Access: Private, Static +// Description: Initializes the XInput library. +//////////////////////////////////////////////////////////////////// +bool XInputDevice:: +init_xinput() { + if (device_cat.is_debug()) { + device_cat.debug() << "Initializing XInput library.\n"; + } + + _initialized = true; + const char *dll_name = "Xinput1_4.dll"; + HMODULE module = LoadLibraryA(dll_name); + + // If we didn't find XInput 1.4, fall back to the older 1.3 version. + if (!module) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Xinput1_4.dll not found, falling back to Xinput1_3.dll\n"; } - if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { - _device_class = DC_gamepad; - } else { - _device_class = DC_unknown; + dll_name = "Xinput1_3.dll"; + module = LoadLibraryA(dll_name); + } + + if (module) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Successfully loaded " << dll_name << "\n"; } - if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { - _flags |= IDF_has_vibration; + // Undocumented version (XInputGetStateEx) that includes a + // state bit for the guide button. + get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100)); + if (get_state == NULL) { + get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState"); + if (get_state == NULL) { + device_cat.error() + << "Failed to find function XInputGetState in " << dll_name << ".\n"; + return false; + } } + + get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); + if (get_capabilities == NULL) { + device_cat.error() + << "Failed to find function XInputGetCapabilities in " << dll_name << ".\n"; + return false; + } + + get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation"); + get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104)); + return true; + } + + device_cat.error() + << "Failed to load Xinput1_4.dll or Xinput1_3.dll.\n"; + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::init_device +// Access: Private +// Description: Initializes the device. Called when the device was +// just connected. +//////////////////////////////////////////////////////////////////// +void XInputDevice:: +init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state) { + if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { + _device_class = DC_gamepad; + } else { + _device_class = DC_unknown; + } + + if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { + _flags |= IDF_has_vibration; } if (get_battery_information != NULL) { XINPUT_BATTERY_INFORMATION batt; if (get_battery_information(_index, 0, &batt) == ERROR_SUCCESS) { - if (batt.BatteryType == BATTERY_TYPE_DISCONNECTED) { - _is_connected = false; - - } else if (batt.BatteryType != BATTERY_TYPE_WIRED) { + if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED && + batt.BatteryType != BATTERY_TYPE_WIRED) { // This device has a battery. Report the battery level. - _is_connected = true; _flags |= IDF_has_battery; _battery_level = batt.BatteryLevel; _max_battery_level = BATTERY_LEVEL_FULL; @@ -163,105 +297,31 @@ XInputDevice(DWORD user_index) : _manufacturer.assign(buffer); } } - } else { - // We need something to name it. - _name = "XInput Device"; - } - - _controls.resize(6); - _buttons.resize(16); - - // Get the initial state. - XINPUT_STATE state; - if (get_state(_index, &state) != ERROR_SUCCESS) { - // Device was disconnected. - _is_connected = false; - return; } WORD buttons = state.Gamepad.wButtons; + WORD mask = 1; + for (int i = 0; i < 16; ++i) { + if (buttons & mask) { + // Set the state without triggering a button event. + _buttons[i]._state = (buttons & mask) ? S_down : S_up; + } + mask <<= 1; + if (i == 10) { + // XInput skips 0x0800. + mask <<= 1; + } + } - set_control_map(0, C_left_trigger); set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); - set_control_map(1, C_right_trigger); set_control_state(1, state.Gamepad.bRightTrigger / 255.0); - set_control_map(2, C_left_x); set_control_state(2, state.Gamepad.sThumbLX / 32767.0); - set_control_map(3, C_left_y); set_control_state(3, state.Gamepad.sThumbLY / 32767.0); - set_control_map(4, C_right_x); set_control_state(4, state.Gamepad.sThumbRX / 32767.0); - set_control_map(5, C_right_y); set_control_state(5, state.Gamepad.sThumbRY / 32767.0); - set_button_map(0, GamepadButton::dpad_up()); - set_button_state(0, (buttons & XINPUT_GAMEPAD_DPAD_UP) != 0); - set_button_map(1, GamepadButton::dpad_down()); - set_button_state(1, (buttons & XINPUT_GAMEPAD_DPAD_DOWN) != 0); - set_button_map(2, GamepadButton::dpad_left()); - set_button_state(2, (buttons & XINPUT_GAMEPAD_DPAD_LEFT) != 0); - set_button_map(3, GamepadButton::dpad_right()); - set_button_state(3, (buttons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0); - set_button_map(4, GamepadButton::start()); - set_button_state(4, (buttons & XINPUT_GAMEPAD_START) != 0); - set_button_map(5, GamepadButton::back()); - set_button_state(5, (buttons & XINPUT_GAMEPAD_BACK) != 0); - set_button_map(6, GamepadButton::lstick()); - set_button_state(6, (buttons & XINPUT_GAMEPAD_LEFT_THUMB) != 0); - set_button_map(7, GamepadButton::rstick()); - set_button_state(7, (buttons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0); - set_button_map(8, GamepadButton::lshoulder()); - set_button_state(8, (buttons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0); - set_button_map(9, GamepadButton::rshoulder()); - set_button_state(9, (buttons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0); - set_button_map(10, GamepadButton::guide()); - set_button_state(10, (buttons & 0x0400) != 0); - set_button_map(11, GamepadButton::action_a()); - set_button_state(11, (buttons & XINPUT_GAMEPAD_A) != 0); - set_button_map(12, GamepadButton::action_b()); - set_button_state(12, (buttons & XINPUT_GAMEPAD_B) != 0); - set_button_map(13, GamepadButton::action_x()); - set_button_state(13, (buttons & XINPUT_GAMEPAD_X) != 0); - set_button_map(14, GamepadButton::action_y()); - set_button_state(14, (buttons & XINPUT_GAMEPAD_Y) != 0); - _last_buttons = buttons; -} - -//////////////////////////////////////////////////////////////////// -// Function: XInputDevice::init -// Access: Protected, Static -// Description: Initializes the XInput library. -//////////////////////////////////////////////////////////////////// -bool XInputDevice:: -init_xinput() { - _initialized = true; - HMODULE module = LoadLibraryA("Xinput1_4.dll"); - if (!module) { - module = LoadLibraryA("Xinput1_3.dll"); - } - if (module) { - // Undocumented version (XInputGetStateEx) that includes a - // state bit for the guide button. - get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100)); - if (get_state == NULL) { - get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState"); - if (get_state == NULL) { - device_cat.error() - << "Failed to find function XInputGetState in XInput DLL.\n"; - return false; - } - } - - get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); - get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation"); - get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104)); - return true; - } - - device_cat.error() - << "Failed to load XInput DLL.\n"; - return false; + _last_packet = state.dwPacketNumber; } //////////////////////////////////////////////////////////////////// @@ -279,10 +339,24 @@ do_poll() { if (get_state(_index, &state) != ERROR_SUCCESS) { // Device was disconnected. - _is_connected = false; + if (_is_connected) { + _is_connected = false; + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->remove_device(this); + } return; - } else { - _is_connected = true; + + } else if (!_is_connected) { + // Device was (re)connected. It's a bit strange to call poll() on + // disconnected device, but there's nothing stopping the user from + // doing so. + XINPUT_CAPABILITIES caps; + if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS) { + _is_connected = true; + init_device(caps, state); + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->add_device(this); + } } if (state.dwPacketNumber == _last_packet) { @@ -299,6 +373,7 @@ do_poll() { } mask <<= 1; if (i == 10) { + // XInput skips 0x0800. mask <<= 1; } } diff --git a/panda/src/device/xInputDevice.h b/panda/src/device/xInputDevice.h index 7abc0eafc5..fea8cd082f 100644 --- a/panda/src/device/xInputDevice.h +++ b/panda/src/device/xInputDevice.h @@ -20,29 +20,33 @@ #ifdef _WIN32 +class InputDeviceManager; + +typedef struct _XINPUT_CAPABILITIES XINPUT_CAPABILITIES; +typedef struct _XINPUT_STATE XINPUT_STATE; + //////////////////////////////////////////////////////////////////// // Class : XInputDevice // Description : This uses Microsoft's XInput library to interface // with an Xbox 360 game controller. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA_DEVICE XInputDevice : public InputDevice { -PUBLISHED: +class EXPCL_PANDA_DEVICE XInputDevice FINAL : public InputDevice { +public: XInputDevice(DWORD user_index); + void detect(InputDeviceManager *mgr); static bool init_xinput(); -public: +private: + void init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state); virtual void do_poll(); private: - DWORD _index; + const DWORD _index; DWORD _last_packet; WORD _last_buttons; static bool _initialized; - - // There are only four of these in existence. - //static XInputDevice _devices[4]; }; #endif // _WIN32 From 307665891d007fdc6894cb5781bc3bc42ea28166 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 17 Dec 2015 01:45:23 +0100 Subject: [PATCH 007/424] Fix compile error on Linux in hotplug code --- panda/src/device/inputDeviceManager.cxx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 44b038adda..0e5907fa3f 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -266,10 +266,8 @@ update() { PT(InputDevice) device = it->second; device->set_connected(false); - InputDevices::iterator it2; _devices_by_path.erase(it); - it2 = std::find(_connected_devices.begin(), _connected_devices.end(), device); - _connected_devices.erase(it2); + _connected_devices.remove_device(device); if (device_cat.is_info()) { device_cat.info() From 70370999c919868849de5a2d31655121eaed9294 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 18 Dec 2015 16:13:50 +0100 Subject: [PATCH 008/424] Map XInput thumbsticks exactly to -1..1 range --- panda/src/device/xInputDevice.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 459d5703d1..227c5a4b01 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -380,10 +380,10 @@ do_poll() { set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); set_control_state(1, state.Gamepad.bRightTrigger / 255.0); - set_control_state(2, state.Gamepad.sThumbLX / 32767.0); - set_control_state(3, state.Gamepad.sThumbLY / 32767.0); - set_control_state(4, state.Gamepad.sThumbRX / 32767.0); - set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + set_control_state(2, (state.Gamepad.sThumbLX + 0.5) / 32767.5); + set_control_state(3, (state.Gamepad.sThumbLY + 0.5) / 32767.5); + set_control_state(4, (state.Gamepad.sThumbRX + 0.5) / 32767.5); + set_control_state(5, (state.Gamepad.sThumbRY + 0.5) / 32767.5); _last_buttons = state.Gamepad.wButtons; _last_packet = state.dwPacketNumber; From ab66108661fc5ea6a94b63fed80562ea72c8439e Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 19 Dec 2015 16:03:28 +0100 Subject: [PATCH 009/424] Robustify Linux gamepad support --- panda/src/device/evdevInputDevice.cxx | 196 +++++++++++++---- panda/src/device/evdevInputDevice.h | 14 +- panda/src/device/inputDevice.I | 39 +++- panda/src/device/inputDevice.h | 16 +- panda/src/device/inputDeviceManager.cxx | 260 ++++++++++++++++------- panda/src/device/inputDeviceManager.h | 8 +- panda/src/device/linuxJoystickDevice.cxx | 109 ++++++++-- panda/src/device/linuxJoystickDevice.h | 8 +- 8 files changed, 510 insertions(+), 140 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 9b69ead27e..191edabbc1 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -19,6 +19,7 @@ #include "gamepadButton.h" #include "keyboardButton.h" #include "mouseButton.h" +#include "inputDeviceManager.h" #include #include @@ -32,19 +33,6 @@ static InputDevice::ControlAxis axis_map[] = { TypeHandle EvdevInputDevice::_type_handle; -//////////////////////////////////////////////////////////////////// -// Function: EvdevInputDevice::Constructor -// Access: Published -// Description: Creates a new device using the Linux joystick -// device using the given file descriptor. It will -// be closed when this object destructs. -//////////////////////////////////////////////////////////////////// -EvdevInputDevice:: -EvdevInputDevice(int fd) : - _fd(fd) { - init_device(); -} - //////////////////////////////////////////////////////////////////// // Function: EvdevInputDevice::Constructor // Access: Published @@ -52,15 +40,18 @@ EvdevInputDevice(int fd) : // device using the given device filename. //////////////////////////////////////////////////////////////////// EvdevInputDevice:: -EvdevInputDevice(const string &fn) { - _fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK); +EvdevInputDevice(int index) : _index(index) { + char path[64]; + sprintf(path, "/dev/input/event%d", index); + + _fd = open(path, O_RDONLY | O_NONBLOCK); if (_fd >= 0) { init_device(); } else { _is_connected = false; device_cat.error() - << "Opening raw input device: " << strerror(errno) << " " << fn << "\n"; + << "Opening raw input device: " << strerror(errno) << " " << path << "\n"; } } @@ -77,6 +68,18 @@ EvdevInputDevice:: } } +//////////////////////////////////////////////////////////////////// +// Function: EvdevInputDevice::check_events +// Access: Public +// Description: Returns true if there are pending events. +//////////////////////////////////////////////////////////////////// +bool EvdevInputDevice:: +check_events() const { + unsigned int avail = 0; + ioctl(_fd, FIONREAD, &avail); + return (avail != 0); +} + //////////////////////////////////////////////////////////////////// // Function: EvdevInputDevice::do_poll // Access: Private, Virtual @@ -88,8 +91,15 @@ EvdevInputDevice:: //////////////////////////////////////////////////////////////////// void EvdevInputDevice:: do_poll() { - if (_fd != -1) { + if (_fd != -1 && process_events()) { while (process_events()) {} + + // If we got events, we are obviously connected. Mark us so. + if (!_is_connected) { + _is_connected = true; + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->add_device(this); + } } } @@ -104,10 +114,8 @@ init_device() { uint8_t evtypes[(EV_CNT + 7) >> 3]; memset(evtypes, 0, sizeof(evtypes)); - char name[256]; - char uniq[256]; + char name[128]; if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || - ioctl(_fd, EVIOCGPHYS(sizeof(uniq)), uniq) < 0 || ioctl(_fd, EVIOCGBIT(0, sizeof(evtypes)), evtypes) < 0) { close(_fd); _fd = -1; @@ -116,29 +124,59 @@ init_device() { return false; } - for (char *p=name; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } - } - for (char *p=uniq; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { - *p = '_'; - } + _name.assign(name); + + struct input_id id; + if (ioctl(_fd, EVIOCGID, &id) >= 0) { + _vendor_id = id.vendor; + _product_id = id.product; } - _name = ((string)name) + "." + uniq; - + bool all_values_zero = true; if (test_bit(EV_ABS, evtypes)) { // Check which axes are on the device. uint8_t axes[(ABS_CNT + 7) >> 3]; memset(axes, 0, sizeof(axes)); + AxisRange range; + range._scale = 1.0; + range._bias = 0.0; + _axis_ranges.resize(ABS_CNT, range); + int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { if (i >= 0 && i < 6) { set_control_map(i, axis_map[i]); + + // Check the initial value and ranges. + struct input_absinfo absinfo; + if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { + double factor, bias; + if (absinfo.minimum < 0) { + // Centered, eg. for sticks. + factor = 2.0 / (absinfo.maximum - absinfo.minimum); + bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum); + } else { + // 0-based, eg. for triggers. + factor = 1.0 / absinfo.maximum; + bias = 0.0; + } + + // Flip Y axis to match Windows implementation. + if (i == ABS_Y || i == ABS_RY) { + factor = -factor; + bias = -bias; + } + + _axis_ranges[i]._scale = factor; + _axis_ranges[i]._bias = bias; + _controls[i]._state = fma(absinfo.value, factor, bias); + + if (absinfo.value != 0) { + all_values_zero = false; + } + } } } } @@ -148,14 +186,40 @@ init_device() { // Check which buttons are on the device. uint8_t keys[(KEY_CNT + 7) >> 3]; memset(keys, 0, sizeof(keys)); + ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys); + + // Also check whether the buttons are currently pressed. + uint8_t states[(KEY_CNT + 7) >> 3]; + memset(states, 0, sizeof(states)); + ioctl(_fd, EVIOCGKEY(sizeof(states)), states); - int num_bits = ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) << 3; int bi = 0; - for (int i = 0; i < num_bits; ++i) { + for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { - set_button_map(bi++, map_button(i)); + set_button_map(bi, map_button(i)); + if (test_bit(i, states)) { + _buttons[bi]._state = S_down; + all_values_zero = false; + } else { + _buttons[bi]._state = S_up; + } + ++bi; } } + + // Check device type. + if (test_bit(BTN_GAMEPAD, keys)) { + _device_class = DC_gamepad; + + } else if (test_bit(BTN_JOYSTICK, keys)) { + _device_class = DC_flight_stick; + + } else if (test_bit(BTN_MOUSE, keys)) { + _device_class = DC_mouse; + + } else if (test_bit(BTN_WHEEL, keys)) { + _device_class = DC_steering_wheel; + } } if (test_bit(EV_REL, evtypes)) { @@ -166,14 +230,52 @@ init_device() { _flags |= IDF_has_vibration; } - _is_connected = true; + char path[64]; + char buffer[256]; + sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); + FILE *f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } + fclose(f); + } + sprintf(path, "/sys/class/input/event%d/device/device/../manufacturer", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + fclose(f); + } + sprintf(path, "/sys/class/input/event%d/device/device/../serial", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + fclose(f); + } + + // Special-case fix for Xbox 360 Wireless Receiver: the Linux kernel + // driver always reports 4 connected gamepads, regardless of the number + // of gamepads actually present. This hack partially remedies this. + if (all_values_zero && _vendor_id == 0x045e && _product_id == 0x0719) { + _is_connected = false; + } else { + _is_connected = true; + } return true; } //////////////////////////////////////////////////////////////////// // Function: EvdevInputDevice::process_events // Access: Private -// Description: Reads a number of events from the device. +// Description: Reads a number of events from the device. Returns +// true if events were read, meaning this function +// should keep being called until it returns false. //////////////////////////////////////////////////////////////////// bool EvdevInputDevice:: process_events() { @@ -186,10 +288,12 @@ process_events() { // No data available for now. } else if (errno == ENODEV || errno == EINVAL) { - // The device ceased to exist, so we better close it. + // The device ceased to exist, so we better close it. No need + // to worry about removing it from the InputDeviceManager, as it + // will get an inotify event sooner or later about this. close(_fd); _fd = -1; - _is_connected = false; + //_is_connected = false; errno = 0; } else { @@ -210,23 +314,31 @@ process_events() { double time = ClockObject::get_global_clock()->get_frame_time(); ButtonHandle button; + // It seems that some devices send a single EV_SYN event when being + // unplugged. Boo. Ignore it. + if (n_read == 1 && events[0].code == EV_SYN) { + return false; + } + for (int i = 0; i < n_read; ++i) { + int code = events[i].code; + switch (events[i].type) { case EV_SYN: break; case EV_REL: - if (events[i].code == REL_X) x += events[i].value; - if (events[i].code == REL_Y) y += events[i].value; + if (code == REL_X) x += events[i].value; + if (code == REL_Y) y += events[i].value; have_pointer = true; break; case EV_ABS: - set_control_state(events[i].code, events[i].value / 32767.0); + set_control_state(code, fma(events[i].value, _axis_ranges[code]._scale, _axis_ranges[code]._bias)); break; case EV_KEY: - button = map_button(events[i].code); + button = map_button(code); _button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time)); break; diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index d3d4203625..a86a7d1434 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -25,11 +25,12 @@ // /dev/input/event# API to read data from a raw mouse. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { -PUBLISHED: - EvdevInputDevice(int fd); - EvdevInputDevice(const string &device); +public: + EvdevInputDevice(int index); virtual ~EvdevInputDevice(); + bool check_events() const; + private: virtual void do_poll(); @@ -37,8 +38,15 @@ private: bool process_events(); private: + int _index; int _fd; + struct AxisRange { + double _scale; + double _bias; + }; + pvector _axis_ranges; + public: static ButtonHandle map_button(int code); diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 8f04226896..5083e3fdda 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -35,7 +35,8 @@ InputDevice() : //////////////////////////////////////////////////////////////////// // Function: InputDevice::get_name // Access: Public -// Description: +// Description: Returns a human-readable name for the device. May +// not necessarily be unique. //////////////////////////////////////////////////////////////////// INLINE string InputDevice:: get_name() const { @@ -43,6 +44,42 @@ get_name() const { return _name; } +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_manufacturer +// Access: Public +// Description: Returns a string containing the manufacturer of +// the device, if this information is known. +//////////////////////////////////////////////////////////////////// +INLINE string InputDevice:: +get_manufacturer() const { + LightMutexHolder holder(_lock); + return _manufacturer; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_vendor_id +// Access: Public +// Description: Returns a string containing the USB vendor ID of +// the device, if this information is known. +//////////////////////////////////////////////////////////////////// +INLINE unsigned short InputDevice:: +get_vendor_id() const { + LightMutexHolder holder(_lock); + return _vendor_id; +} + +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::get_product_id +// Access: Public +// Description: Returns a string containing the USB product ID of +// the device, if this information is known. +//////////////////////////////////////////////////////////////////// +INLINE unsigned short InputDevice:: +get_product_id() const { + LightMutexHolder holder(_lock); + return _product_id; +} + //////////////////////////////////////////////////////////////////// // Function: InputDevice::is_connected // Access: Public diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index a6aaa5a015..5e33115a3e 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -111,12 +111,24 @@ PUBLISHED: }; INLINE string get_name() const; + INLINE string get_manufacturer() const; + INLINE unsigned short get_vendor_id() const; + INLINE unsigned short get_product_id() const; INLINE bool is_connected() const; INLINE DeviceClass get_device_class() const; // The human-readable name of this input device. MAKE_PROPERTY(name, get_name); + // The device's manufacturer, or the empty string if not known. + MAKE_PROPERTY(manufacturer, get_manufacturer); + + // USB vendor ID of the device, or 0 if not known. + MAKE_PROPERTY(vendor_id, get_vendor_id); + + // USB product ID of the device, or 0 if not known. + MAKE_PROPERTY(product_id, get_product_id); + // This is false if we know that the device is not currently connected. // May report false positives if we can't know this with certainty. MAKE_PROPERTY(connected, is_connected); @@ -212,8 +224,8 @@ protected: DeviceClass _device_class; int _flags; int _event_sequence; - short _vendor_id; - short _product_id; + unsigned short _vendor_id; + unsigned short _product_id; bool _is_connected; bool _enable_pointer_events; PointerData _pointer_data; diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 0e5907fa3f..e160a70746 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -39,33 +39,6 @@ InputDeviceManager *InputDeviceManager::_global_ptr = NULL; #ifdef PHAVE_LINUX_INPUT_H InputDeviceManager:: InputDeviceManager() : _inotify_fd(-1) { - - // Scan /dev/input for a list of input devices. - DIR *dir = opendir("/dev/input"); - if (dir) { - dirent *entry = readdir(dir); - while (entry != NULL) { - if (entry->d_type == DT_CHR) { - string name(entry->d_name); - if (!consider_add_linux_device(name)) { - // We can't access it. That's pretty normal for most devices. - if (device_cat.is_debug()) { - device_cat.debug() - << "Ignoring input device /dev/input/" << name << ": " - << strerror(errno) << "\n"; - } - errno = 0; - } - } - entry = readdir(dir); - } - closedir(dir); - } else { - device_cat.error() - << "Error opening directory /dev/input: " << strerror(errno) << "\n"; - return; - } - // Use inotify to watch /dev/input for hotplugging of devices. _inotify_fd = inotify_init1(O_NONBLOCK); @@ -77,6 +50,35 @@ InputDeviceManager() : _inotify_fd(-1) { device_cat.error() << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; } + + // Scan /dev/input for a list of input devices. + DIR *dir = opendir("/dev/input"); + if (dir) { + vector_int indices; + dirent *entry = readdir(dir); + while (entry != NULL) { + int index = -1; + if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) { + indices.push_back(index); + } + entry = readdir(dir); + } + closedir(dir); + + // We'll want to sort the devices by index, since the order may be + // meaningful (eg. for the Xbox wireless receiver). + std::sort(indices.begin(), indices.end()); + _evdev_devices.resize(indices.back() + 1, NULL); + + vector_int::const_iterator it; + for (it = indices.begin(); it != indices.end(); ++it) { + consider_add_evdev_device(*it); + } + } else { + device_cat.error() + << "Error opening directory /dev/input: " << strerror(errno) << "\n"; + return; + } } #elif defined(_WIN32) InputDeviceManager:: @@ -129,41 +131,124 @@ InputDeviceManager:: #ifdef PHAVE_LINUX_INPUT_H //////////////////////////////////////////////////////////////////// -// Function: InputDeviceManager::consider_add_linux_device +// Function: InputDeviceManager::consider_add_evdev_device // Access: Private -// Description: Checks whether the given device is accessible, and -// if so, adds it. Returns false on error. +// Description: Checks whether the event device with the given index +// is accessible, and if so, adds it. Returns the +// device if it was newly connected. //////////////////////////////////////////////////////////////////// -bool InputDeviceManager:: -consider_add_linux_device(const string &name) { - // Get the full path name first. - string path = "/dev/input/"; - path += name; - - if (access(path.c_str(), R_OK) < 0) { - return false; +InputDevice *InputDeviceManager:: +consider_add_evdev_device(int ev_index) { + if (ev_index < _evdev_devices.size()) { + if (_evdev_devices[ev_index] != NULL) { + // We already have this device. FIXME: probe it and add it to the + // list of connected devices? + return NULL; + } + } else { + // Make room to store this index. + _evdev_devices.resize(ev_index + 1, NULL); } - if (_devices_by_path.count(name)) { - // We already have this device. - return true; - } + // Check if we can directly read the event device. + char path[64]; + sprintf(path, "/dev/input/event%d", ev_index); - // Check if it's a joystick or game controller device. - if (name.size() > 2 && name[0] == 'j' && name[1] == 's' && isdigit(name[2])) { - PT(InputDevice) device = new LinuxJoystickDevice(path); - if (device_cat.is_info()) { - device_cat.info() - << "Discovered input device " << *device << "\n"; + if (access(path, R_OK) == 0) { + PT(InputDevice) device = new EvdevInputDevice(ev_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered evdev input device " << *device << "\n"; } - _devices_by_path[name] = device; - _connected_devices.add_device(MOVE(device)); - return true; + _evdev_devices[ev_index] = device; + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return _evdev_devices[ev_index]; } - return true; + // Nope. The permissions haven't been configured to allow it. + // Check if this corresponds to a /dev/input/jsX interface, which has + // a better chance of having read permissions set, but doesn't export + // all of the features (notably, force feedback). + + // We do this by checking for a js# directory inside the sysfs + // device directory. + sprintf(path, "/sys/class/input/event%d/device", ev_index); + + DIR *dir = opendir(path); + if (dir == NULL) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Error opening directory " << path << ": " << strerror(errno) << "\n"; + } + return NULL; + } + + dirent *entry = readdir(dir); + while (entry != NULL) { + int js_index = -1; + if (sscanf(entry->d_name, "js%d", &js_index) == 1) { + // Yes, we fonud a corresponding js device. Try adding it. + closedir(dir); + + InputDevice *device = consider_add_js_device(js_index); + if (device != NULL && device_cat.is_warning()) { + // This seemed to work. Display a warning to the user indicating + // that they might want to configure udev properly. + device_cat.warning() + << "Some features of " << device->get_device_class() + << " device " << device->get_name() << " are not available due" + " to lack of read permissions on /dev/input/event" << ev_index + << ".\n"; + } + _evdev_devices[ev_index] = device; + return device; + } + entry = readdir(dir); + } + + closedir(dir); + return NULL; } + +//////////////////////////////////////////////////////////////////// +// Function: InputDeviceManager::consider_add_evdev_device +// Access: Private +// Description: Checks whether the joystick device with the given +// index is accessible, and if so, adds it. Returns +// the device if it was newly connected. +//////////////////////////////////////////////////////////////////// +InputDevice *InputDeviceManager:: +consider_add_js_device(int js_index) { + char path[64]; + sprintf(path, "/dev/input/js%d", js_index); + + if (access(path, R_OK) == 0) { + PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(js_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered joydev input device " << *device << "\n"; + } + InputDevice *device_p = device.p(); + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return device_p; + } + + return NULL; +} + #endif //////////////////////////////////////////////////////////////////// @@ -199,6 +284,12 @@ add_device(InputDevice *device) { { LightMutexHolder holder(_lock); _connected_devices.add_device(device); + +#ifdef PHAVE_LINUX_INPUT_H + // If we had added it pending activity on the device, remove it + // from the list of inactive devices. + _inactive_devices.remove_device(device); +#endif } throw_event("connect-device", device); } @@ -229,9 +320,23 @@ remove_device(InputDevice *device) { void InputDeviceManager:: update() { #ifdef PHAVE_LINUX_INPUT_H + // Check for any devices that may be disconnected and need to be probed + // in order to see whether they have been reconnected. + InputDeviceSet inactive_devices; + { + LightMutexHolder holder(_lock); + inactive_devices = _inactive_devices; + } + for (size_t i = 0; i < inactive_devices.size(); ++i) { + InputDevice *device = inactive_devices[i]; + if (device != NULL && !device->is_connected()) { + device->poll(); + } + } + // We use inotify to tell us whether a device was added, removed, // or has changed permissions to allow us to access it. - unsigned int avail; + unsigned int avail = 0; ioctl(_inotify_fd, FIONREAD, &avail); if (avail == 0) { return; @@ -261,34 +366,39 @@ update() { if (event->mask & IN_DELETE) { // The device was deleted. If we have it, remove it. - DevicesByPath::iterator it = _devices_by_path.find(name); - if (it != _devices_by_path.end()) { - PT(InputDevice) device = it->second; - device->set_connected(false); - _devices_by_path.erase(it); - _connected_devices.remove_device(device); + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + // Check if we have this evdev device. If so, disconnect it. + if (index < _evdev_devices.size()) { + PT(InputDevice) device = _evdev_devices[index]; + if (device != NULL) { + _evdev_devices[index] = NULL; + _inactive_devices.remove_device(device); + if (_connected_devices.remove_device(device)) { + throw_event("disconnect-device", device.p()); + } - if (device_cat.is_info()) { - device_cat.info() - << "Removed input device " << *device << "\n"; + if (device_cat.is_debug()) { + device_cat.debug() + << "Removed input device " << *device << "\n"; + } + } } - throw_event("disconnect-device", device.p()); } } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { - // The device was created, or it was chmodded to be accessible. - DevicesByPath::iterator it = _devices_by_path.find(name); - if (it == _devices_by_path.end()) { - // We don't know about this device yet. - if (!consider_add_linux_device(name) && (event->mask & IN_CREATE) != 0) { - if (device_cat.is_debug()) { - device_cat.debug() - << "Ignoring input device /dev/input/" << name << ": " - << strerror(errno) << "\n"; - } + // The device was created, or it was chmodded to be accessible. We + // need to check for the latter since it seems that the device may + // get the IN_CREATE event before the driver gets the permissions + // set properly. + + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + InputDevice *device = consider_add_evdev_device(index); + if (device != NULL && device->is_connected()) { + throw_event("connect-device", device); } - errno = 0; } } @@ -301,6 +411,8 @@ update() { // check if it's connected every so often. Perhaps we can switch to // using RegisterDeviceNotification in the future. double time_now = ClockObject::get_global_clock()->get_real_time(); + LightMutexHolder holder(_update_lock); + if (time_now - _last_detection > xinput_detection_delay.get_value()) { // I've heard this can be quite slow if no device is detected. We // should probably move it to a thread. diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 3f2c2cea43..2c96a5876f 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -36,7 +36,8 @@ private: ~InputDeviceManager(); #ifdef PHAVE_LINUX_INPUT_H - bool consider_add_linux_device(const string &name); + InputDevice *consider_add_evdev_device(int index); + InputDevice *consider_add_js_device(int index); #endif public: @@ -59,12 +60,13 @@ private: #ifdef PHAVE_LINUX_INPUT_H int _inotify_fd; - typedef pmap DevicesByPath; - DevicesByPath _devices_by_path; + pvector _evdev_devices; + InputDeviceSet _inactive_devices; #endif #ifdef _WIN32 // There are always exactly four of these in existence. + LightMutex _update_lock; XInputDevice _xinput_device0; XInputDevice _xinput_device1; XInputDevice _xinput_device2; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index b3566554e6..f15b4cf1dd 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -30,14 +30,14 @@ TypeHandle LinuxJoystickDevice::_type_handle; // device using the given device filename. //////////////////////////////////////////////////////////////////// LinuxJoystickDevice:: -LinuxJoystickDevice(const string &device) : +LinuxJoystickDevice(int index) : _fd(-1), - _device(device) + _index(index) { LightMutexHolder holder(_lock); if (!open_device()) { device_cat.error() - << "Could not open joystick device " << _device + << "Could not open joystick device /dev/input/js" << index << ": " << strerror(errno) << "\n"; } } @@ -55,6 +55,18 @@ LinuxJoystickDevice:: } } +//////////////////////////////////////////////////////////////////// +// Function: LinuxJoystickDevice::check_events +// Access: Public +// Description: Returns true if there are pending events. +//////////////////////////////////////////////////////////////////// +bool LinuxJoystickDevice:: +check_events() const { + unsigned int avail = 0; + ioctl(_fd, FIONREAD, &avail); + return (avail != 0); +} + //////////////////////////////////////////////////////////////////// // Function: LinuxJoystickDevice::do_poll // Access: Private, Virtual @@ -66,10 +78,15 @@ LinuxJoystickDevice:: //////////////////////////////////////////////////////////////////// void LinuxJoystickDevice:: do_poll() { - if (_fd != -1) { + if (_fd != -1 && process_events()) { while (process_events()) {} - } else { - open_device(); + + // If we got events, we are obviously connected. Mark us so. + if (!_is_connected) { + _is_connected = true; + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + mgr->add_device(this); + } } } @@ -84,14 +101,18 @@ bool LinuxJoystickDevice:: open_device() { nassertr(_lock.debug_is_locked(), false); - _fd = open(_device.c_str(), O_RDONLY | O_NONBLOCK); + char path[64]; + sprintf(path, "/dev/input/js%d", _index); + + _fd = open(path, O_RDONLY | O_NONBLOCK); if (_fd == -1) { _is_connected = false; return false; } - // Read the name from the device. + // Read the name from the device. We'll later try to use sysfs to read + // the proper product name from the device, but this is a good fallback. char name[128]; name[0] = 0; ioctl(_fd, JSIOCGNAME(sizeof(name)), name); @@ -249,16 +270,76 @@ open_device() { } } + // Get additional information from sysfs. + sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index); + FILE *f = fopen(path, "r"); + if (f) { + fscanf(f, "%hx", &_vendor_id); + fclose(f); + } + sprintf(path, "/sys/class/input/js%d/device/id/product", _index); + f = fopen(path, "r"); + if (f) { + fscanf(f, "%hx", &_product_id); + fclose(f); + } + char buffer[256]; + sprintf(path, "/sys/class/input/js%d/device/device/../product", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } + fclose(f); + } + sprintf(path, "/sys/class/input/js%d/device/device/../manufacturer", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + fclose(f); + } + sprintf(path, "/sys/class/input/js%d/device/device/../serial", _index); + f = fopen(path, "r"); + if (f) { + fgets(buffer, sizeof(buffer), f); + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + fclose(f); + } + // Read the init events. - _is_connected = true; while (process_events()) {}; + + // Special case handling for the wireless Xbox receiver - the Linux + // joystick API doesn't seem to have a way to report whether the device + // is actually turned on. The best we can do is check whether the axes + // are all 0, which indicates that the driver hasn't received any data for + // this gamepad yet (which means it hasn't been plugged in for this session) + if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) { + for (int i = 0; i < _controls.size(); ++i) { + if (_controls[i]._state != 0.0) { + _is_connected = true; + return true; + } + } + _is_connected = false; + } else { + _is_connected = true; + } + return true; } //////////////////////////////////////////////////////////////////// // Function: LinuxJoystickDevice::process_events // Access: Private -// Description: Reads a number of events from the joystick. +// Description: Reads a number of events from the device. Returns +// true if events were read, meaning this function +// should keep being called until it returns false. //////////////////////////////////////////////////////////////////// bool LinuxJoystickDevice:: process_events() { @@ -271,10 +352,12 @@ process_events() { // No data available for now. } else if (errno == ENODEV) { - // The device ceased to exist, so we better close it. + // The device ceased to exist, so we better close it. No need + // to worry about removing it from the InputDeviceManager, as it + // will get an inotify event sooner or later about this. close(_fd); _fd = -1; - _is_connected = false; + //_is_connected = false; errno = 0; } else { @@ -301,6 +384,8 @@ process_events() { if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { // We'd like to use 0.0 to indicate the resting position. set_control_state(index, (events[i].value + 32767) / 65534.0); + } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { + set_control_state(index, events[i].value / -32767.0); } else { set_control_state(index, events[i].value / 32767.0); } diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index d71ad45c17..c2952d061f 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -25,10 +25,12 @@ // /dev/input/js# API to read data from a game controller. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice { -PUBLISHED: - LinuxJoystickDevice(const string &device); +public: + LinuxJoystickDevice(int index); virtual ~LinuxJoystickDevice(); + bool check_events() const; + private: virtual void do_poll(); @@ -37,7 +39,7 @@ private: private: int _fd; - string _device; + int _index; public: static TypeHandle get_class_type() { From 687e080bdd9e718021808a9d895540395096c215 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 19 Dec 2015 19:32:52 +0100 Subject: [PATCH 010/424] Add gamepad rumble support on Linux --- panda/src/device/evdevInputDevice.cxx | 102 +++++++++++++++++++++--- panda/src/device/evdevInputDevice.h | 8 +- panda/src/device/inputDevice.I | 15 ++++ panda/src/device/inputDevice.cxx | 12 +++ panda/src/device/inputDevice.h | 3 + panda/src/device/inputDeviceManager.cxx | 6 +- 6 files changed, 129 insertions(+), 17 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 191edabbc1..73e247462b 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -40,11 +40,25 @@ TypeHandle EvdevInputDevice::_type_handle; // device using the given device filename. //////////////////////////////////////////////////////////////////// EvdevInputDevice:: -EvdevInputDevice(int index) : _index(index) { +EvdevInputDevice(int index) : + _index(index), + _fd(-1), + _can_write(false), + _ff_id(-1), + _ff_playing(false), + _ff_strong(-1), + _ff_weak(-1) { + char path[64]; sprintf(path, "/dev/input/event%d", index); - _fd = open(path, O_RDONLY | O_NONBLOCK); + _fd = open(path, O_RDWR | O_NONBLOCK); + if (_fd >= 0) { + _can_write = true; + } else { + // On failure, open device as read-only. + _fd = open(path, O_RDONLY | O_NONBLOCK); + } if (_fd >= 0) { init_device(); @@ -63,21 +77,74 @@ EvdevInputDevice(int index) : _index(index) { EvdevInputDevice:: ~EvdevInputDevice() { if (_fd != -1) { + if (_ff_id != -1) { + // Remove force-feedback effect. + do_set_vibration(0, 0); + ioctl(_fd, EVIOCRMFF, _ff_id); + _ff_id = -1; + } + close(_fd); _fd = -1; } } //////////////////////////////////////////////////////////////////// -// Function: EvdevInputDevice::check_events -// Access: Public -// Description: Returns true if there are pending events. +// Function: EvdevInputDevice::do_set_vibration +// Access: Private, Virtual +// Description: Sets the vibration strength. The first argument +// controls a low-frequency motor, if present, and +// the latter controls a high-frequency motor. The +// values are within the 0-1 range. //////////////////////////////////////////////////////////////////// -bool EvdevInputDevice:: -check_events() const { - unsigned int avail = 0; - ioctl(_fd, FIONREAD, &avail); - return (avail != 0); +void EvdevInputDevice:: +do_set_vibration(double strong, double weak) { + if (_fd == -1 || !_can_write) { + return; + } + + int strong_level = strong * 0xffff; + int weak_level = weak * 0xffff; + + if (strong_level == _ff_strong && weak_level == _ff_weak) { + // No change. + return; + } + + // Upload the new effect parameters. Do this even if we are about + // to stop the effect, because some drivers don't respond to simply + // stopping the effect. + struct ff_effect effect; + effect.type = FF_RUMBLE; + effect.id = _ff_id; + effect.direction = 0; + effect.trigger.button = 0; + effect.trigger.interval = 0; + effect.replay.length = 0; + effect.replay.delay = 0; + effect.u.rumble.strong_magnitude = strong_level; + effect.u.rumble.weak_magnitude = weak_level; + + if (ioctl(_fd, EVIOCSFF, &effect) < 0) { + return; + } else { + _ff_id = effect.id; + _ff_strong = strong_level; + _ff_weak = weak_level; + } + + if (!_ff_playing) { + // Start the effect. We could pass 0 as value to stop the effect + // when a level of 0 is requested, but my driver seems to ignore it. + _ff_playing = true; + + struct input_event play; + play.type = EV_FF; + play.code = _ff_id; + play.value = 1; + + write(_fd, &play, sizeof(play)); + } } //////////////////////////////////////////////////////////////////// @@ -227,7 +294,20 @@ init_device() { } if (test_bit(EV_FF, evtypes)) { - _flags |= IDF_has_vibration; + uint8_t effects[(FF_CNT + 7) >> 3]; + memset(effects, 0, sizeof(effects)); + ioctl(_fd, EVIOCGBIT(EV_FF, sizeof(effects)), effects); + + if (test_bit(FF_RUMBLE, effects)) { + if (_can_write) { + _flags |= IDF_has_vibration; + } else { + // Let the user know what he's missing out on. + device_cat.warning() + << "/dev/input/event" << _index << " is not writable, vibration " + << "effects will be unavailable.\n"; + } + } } char path[64]; diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index a86a7d1434..6eff8534a5 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -29,9 +29,8 @@ public: EvdevInputDevice(int index); virtual ~EvdevInputDevice(); - bool check_events() const; - private: + virtual void do_set_vibration(double strong, double weak); virtual void do_poll(); bool init_device(); @@ -40,6 +39,11 @@ private: private: int _index; int _fd; + bool _can_write; + int _ff_id; + bool _ff_playing; + int _ff_strong; + int _ff_weak; struct AxisRange { double _scale; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 5083e3fdda..06e8d2227e 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -393,6 +393,21 @@ is_control_known(int index) const { } } +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::set_vibration +// Access: Public +// Description: Sets the strength of the vibration effect, if +// supported. The values are clamped to 0-1 range. +// The first value controls the low-frequency rumble +// motor, whereas the second controls the high-frequency +// motor, if present. +//////////////////////////////////////////////////////////////////// +INLINE void InputDevice:: +set_vibration(double strong, double weak) { + LightMutexHolder holder(_lock); + do_set_vibration(max(min(strong, 1.0), 0.0), max(min(weak, 1.0), 0.0)); +} + //////////////////////////////////////////////////////////////////// // Function: InputDevice::enable_pointer_events // Access: Public diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 4637673672..0b4279e34e 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -417,6 +417,18 @@ write_controls(ostream &out, int indent_level) const { } } +//////////////////////////////////////////////////////////////////// +// Function: InputDevice::do_set_vibration +// Access: Private, Virtual +// Description: Sets the vibration strength. The first argument +// controls a low-frequency motor, if present, and +// the latter controls a high-frequency motor. The +// values are within the 0-1 range. +//////////////////////////////////////////////////////////////////// +void InputDevice:: +do_set_vibration(double strong, double weak) { +} + //////////////////////////////////////////////////////////////////// // Function: InputDevice::do_poll // Access: Protected, Virtual diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 5e33115a3e..99a8e059ca 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -160,6 +160,8 @@ PUBLISHED: INLINE double get_control_state(int index) const; INLINE bool is_control_known(int index) const; + INLINE void set_vibration(double strong, double weak); + INLINE void enable_pointer_events(); INLINE void disable_pointer_events(); @@ -179,6 +181,7 @@ protected: void set_control_state(int index, double state); void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); + virtual void do_set_vibration(double low, double high); virtual void do_poll(); public: diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index e160a70746..1b12786d62 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -202,10 +202,8 @@ consider_add_evdev_device(int ev_index) { // This seemed to work. Display a warning to the user indicating // that they might want to configure udev properly. device_cat.warning() - << "Some features of " << device->get_device_class() - << " device " << device->get_name() << " are not available due" - " to lack of read permissions on /dev/input/event" << ev_index - << ".\n"; + << "/dev/input/event" << ev_index << " is not readable, some " + "features will be unavailable.\n"; } _evdev_devices[ev_index] = device; return device; From bfea37f64d5fcf8f16a1eeae7139276cf99f028b Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 19 Dec 2015 20:09:07 +0100 Subject: [PATCH 011/424] Detect keyboard devices on Linux --- panda/src/device/evdevInputDevice.cxx | 7 +++++++ panda/src/device/inputDevice.cxx | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 73e247462b..3d7388f3bf 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -274,6 +274,10 @@ init_device() { } } + if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) { + _flags |= IDF_has_keyboard; + } + // Check device type. if (test_bit(BTN_GAMEPAD, keys)) { _device_class = DC_gamepad; @@ -286,6 +290,9 @@ init_device() { } else if (test_bit(BTN_WHEEL, keys)) { _device_class = DC_steering_wheel; + + } else if (_flags & IDF_has_keyboard) { + _device_class = DC_keyboard; } } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 0b4279e34e..f1e6a7fbb6 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -283,11 +283,17 @@ output(ostream &out) const { } if (_buttons.size() > 0) { - out << ", " << _buttons.size() << " buttons"; + out << ", " << _buttons.size() << " button"; + if (_buttons.size() != 1) { + out.put('s'); + } } if (_controls.size() > 0) { - out << ", " << _controls.size() << " controls"; + out << ", " << _controls.size() << " control"; + if (_controls.size() != 1) { + out.put('s'); + } } if (_flags & IDF_has_pointer) { From 44f9b2232361b75640d77195bed7342a1a9fb4a8 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 19 Dec 2015 21:04:49 +0100 Subject: [PATCH 012/424] Gamepad vibration on Windows --- dtool/src/parser-inc/XInput.h | 1 + panda/src/device/xInputDevice.cxx | 38 ++++++++++++++++++++++++++++++- panda/src/device/xInputDevice.h | 4 +++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/dtool/src/parser-inc/XInput.h b/dtool/src/parser-inc/XInput.h index f770cf3d71..21a2802287 100644 --- a/dtool/src/parser-inc/XInput.h +++ b/dtool/src/parser-inc/XInput.h @@ -1,2 +1,3 @@ struct XINPUT_STATE; struct XINPUT_CAPABILITIES; +struct XINPUT_VIBRATION; diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 227c5a4b01..290cfb12f3 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -63,11 +63,13 @@ struct XINPUT_BUSINFO { }; typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); +typedef DWORD (*pXInputSetState)(DWORD, XINPUT_VIBRATION *); typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *); static pXInputGetState get_state = NULL; +static pXInputSetState set_state = NULL; static pXInputGetCapabilities get_capabilities = NULL; static pXInputGetBatteryInformation get_battery_information = NULL; static pXInputGetBaseBusInformation get_base_bus_information = NULL; @@ -132,6 +134,17 @@ XInputDevice(DWORD user_index) : } } +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::Constructor +// Access: Published +// Description: Creates a new device using the Linux joystick +// device using the given device filename. +//////////////////////////////////////////////////////////////////// +XInputDevice:: +~XInputDevice() { + do_set_vibration(0, 0); +} + //////////////////////////////////////////////////////////////////// // Function: XInputDevice::detect // Access: Public @@ -210,6 +223,13 @@ init_xinput() { } } + set_state = (pXInputSetState)GetProcAddress(module, "XInputSetState"); + if (set_state == NULL) { + device_cat.error() + << "Failed to find function XInputSetState in " << dll_name << ".\n"; + return false; + } + get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); if (get_capabilities == NULL) { device_cat.error() @@ -234,7 +254,7 @@ init_xinput() { // just connected. //////////////////////////////////////////////////////////////////// void XInputDevice:: -init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state) { +init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { _device_class = DC_gamepad; } else { @@ -324,6 +344,22 @@ init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state) { _last_packet = state.dwPacketNumber; } +//////////////////////////////////////////////////////////////////// +// Function: XInputDevice::do_set_vibration +// Access: Private, Virtual +// Description: Sets the vibration strength. The first argument +// controls a low-frequency motor, if present, and +// the latter controls a high-frequency motor. The +// values are within the 0-1 range. +//////////////////////////////////////////////////////////////////// +void XInputDevice:: +do_set_vibration(double strong, double weak) { + XINPUT_VIBRATION vibration; + vibration.wLeftMotorSpeed = strong * 0xffff; + vibration.wRightMotorSpeed = weak * 0xffff; + set_state(_index, &vibration); +} + //////////////////////////////////////////////////////////////////// // Function: InputDevice::do_poll // Access: Public, Virtual diff --git a/panda/src/device/xInputDevice.h b/panda/src/device/xInputDevice.h index fea8cd082f..3bfc9fe728 100644 --- a/panda/src/device/xInputDevice.h +++ b/panda/src/device/xInputDevice.h @@ -33,12 +33,14 @@ typedef struct _XINPUT_STATE XINPUT_STATE; class EXPCL_PANDA_DEVICE XInputDevice FINAL : public InputDevice { public: XInputDevice(DWORD user_index); + ~XInputDevice(); void detect(InputDeviceManager *mgr); static bool init_xinput(); private: - void init_device(XINPUT_CAPABILITIES &caps, XINPUT_STATE &state); + void init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state); + virtual void do_set_vibration(double strong, double weak); virtual void do_poll(); private: From 01e57e1bdd77400050c44f55c602e4cd3d698389 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Jun 2016 10:54:03 +0200 Subject: [PATCH 013/424] Emulate d-pad from hat axes, fixes Xbox One d-pad buttons on Linux --- panda/src/device/evdevInputDevice.cxx | 126 ++++++++++++++--------- panda/src/device/evdevInputDevice.h | 6 ++ panda/src/device/inputDevice.I | 10 ++ panda/src/device/inputDevice.h | 1 + panda/src/device/linuxJoystickDevice.cxx | 50 ++++++++- panda/src/device/linuxJoystickDevice.h | 6 ++ 6 files changed, 147 insertions(+), 52 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index f3131d0f1d..ef318c3e5d 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -43,7 +43,11 @@ EvdevInputDevice(int index) : _ff_id(-1), _ff_playing(false), _ff_strong(-1), - _ff_weak(-1) { + _ff_weak(-1), + _dpad_x_axis(-1), + _dpad_y_axis(-1), + _dpad_left_button(-1), + _dpad_up_button(-1) { char path[64]; sprintf(path, "/dev/input/event%d", index); @@ -185,54 +189,7 @@ init_device() { } bool all_values_zero = true; - if (test_bit(EV_ABS, evtypes)) { - // Check which axes are on the device. - uint8_t axes[(ABS_CNT + 7) >> 3]; - memset(axes, 0, sizeof(axes)); - - AxisRange range; - range._scale = 1.0; - range._bias = 0.0; - _axis_ranges.resize(ABS_CNT, range); - - int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; - for (int i = 0; i < num_bits; ++i) { - if (test_bit(i, axes)) { - if (i >= 0 && i < 6) { - set_control_map(i, axis_map[i]); - - // Check the initial value and ranges. - struct input_absinfo absinfo; - if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { - double factor, bias; - if (absinfo.minimum < 0) { - // Centered, eg. for sticks. - factor = 2.0 / (absinfo.maximum - absinfo.minimum); - bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum); - } else { - // 0-based, eg. for triggers. - factor = 1.0 / absinfo.maximum; - bias = 0.0; - } - - // Flip Y axis to match Windows implementation. - if (i == ABS_Y || i == ABS_RY) { - factor = -factor; - bias = -bias; - } - - _axis_ranges[i]._scale = factor; - _axis_ranges[i]._bias = bias; - _controls[i]._state = fma(absinfo.value, factor, bias); - - if (absinfo.value != 0) { - all_values_zero = false; - } - } - } - } - } - } + bool have_dpad_buttons = false; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. @@ -255,6 +212,9 @@ init_device() { } else { _buttons[bi]._state = S_up; } + if (_buttons[bi]._handle == GamepadButton::dpad_left()) { + have_dpad_buttons = true; + } ++bi; } } @@ -281,6 +241,67 @@ init_device() { } } + if (test_bit(EV_ABS, evtypes)) { + // Check which axes are on the device. + uint8_t axes[(ABS_CNT + 7) >> 3]; + memset(axes, 0, sizeof(axes)); + + AxisRange range; + range._scale = 1.0; + range._bias = 0.0; + _axis_ranges.resize(ABS_CNT, range); + + int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; + for (int i = 0; i < num_bits; ++i) { + if (test_bit(i, axes)) { + set_control_map(i, axis_map[i]); + + // Check the initial value and ranges. + struct input_absinfo absinfo; + if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { + double factor, bias; + if (absinfo.minimum < 0) { + // Centered, eg. for sticks. + factor = 2.0 / (absinfo.maximum - absinfo.minimum); + bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum); + } else { + // 0-based, eg. for triggers. + factor = 1.0 / absinfo.maximum; + bias = 0.0; + } + + // Flip Y axis to match Windows implementation. + if (i == ABS_Y || i == ABS_RY) { + factor = -factor; + bias = -bias; + } + + _axis_ranges[i]._scale = factor; + _axis_ranges[i]._bias = bias; + _controls[i]._state = fma(absinfo.value, factor, bias); + + if (absinfo.value != 0) { + all_values_zero = false; + } + } + + // Emulate D-Pad buttons if necessary. + if (i == ABS_HAT0X && !have_dpad_buttons) { + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } + if (i == ABS_HAT0Y && !have_dpad_buttons) { + _dpad_y_axis = i; + _dpad_up_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } + } + } + } + if (test_bit(EV_REL, evtypes)) { _flags |= IDF_has_pointer; } @@ -403,6 +424,13 @@ process_events() { break; case EV_ABS: + if (code == _dpad_x_axis) { + set_button_state(_dpad_left_button, events[i].value < 0); + set_button_state(_dpad_left_button+1, events[i].value > 0); + } else if (code == _dpad_y_axis) { + set_button_state(_dpad_up_button, events[i].value < 0); + set_button_state(_dpad_up_button+1, events[i].value > 0); + } set_control_state(code, fma(events[i].value, _axis_ranges[code]._scale, _axis_ranges[code]._bias)); break; diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 772342a946..5a604b8617 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -50,6 +50,12 @@ private: }; pvector _axis_ranges; + // These are used for D-pad emulation. + int _dpad_x_axis; + int _dpad_y_axis; + int _dpad_left_button; + int _dpad_up_button; + public: static ButtonHandle map_button(int code); diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index d1c6cdf5eb..e63930984b 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -382,6 +382,16 @@ ButtonState() : { } +/** + * + */ +INLINE InputDevice::ButtonState:: +ButtonState(ButtonHandle handle) : + _handle(handle), + _state(S_unknown) +{ +} + /** * */ diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 889ffec2f8..0df79c3c65 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -238,6 +238,7 @@ public: class ButtonState { public: INLINE ButtonState(); + INLINE ButtonState(ButtonHandle handle); ButtonHandle _handle; State _state; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 826b4ac442..ed4e7fa329 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -28,7 +28,11 @@ TypeHandle LinuxJoystickDevice::_type_handle; LinuxJoystickDevice:: LinuxJoystickDevice(int index) : _fd(-1), - _index(index) + _index(index), + _dpad_x_axis(-1), + _dpad_y_axis(-1), + _dpad_left_button(-1), + _dpad_up_button(-1) { LightMutexHolder holder(_lock); if (!open_device()) { @@ -115,7 +119,7 @@ open_device() { uint16_t btnmap[512]; ioctl(_fd, JSIOCGBTNMAP, btnmap); - for (char i = 0; i < num_buttons; ++i) { + for (uint8_t i = 0; i < num_buttons; ++i) { ButtonHandle handle = ButtonHandle::none(); switch (btnmap[i]) { case BTN_A: @@ -181,6 +185,7 @@ open_device() { case BTN_TRIGGER_HAPPY1: handle = GamepadButton::dpad_left(); + _dpad_left_button = i; break; case BTN_TRIGGER_HAPPY2: @@ -189,6 +194,7 @@ open_device() { case BTN_TRIGGER_HAPPY3: handle = GamepadButton::dpad_up(); + _dpad_up_button = i; break; case BTN_TRIGGER_HAPPY4: @@ -196,6 +202,10 @@ open_device() { break; default: + if (device_cat.is_debug()) { + device_cat.debug() << "Unmapped /dev/input/js" << _index + << " button " << (int)i << ": 0x" << hex << btnmap[i] << "\n"; + } handle = ButtonHandle::none(); break; } @@ -207,7 +217,7 @@ open_device() { uint8_t axmap[512]; ioctl(_fd, JSIOCGAXMAP, axmap); - for (char i = 0; i < num_axes; ++i) { + for (uint8_t i = 0; i < num_axes; ++i) { ControlAxis axis = C_none; switch (axmap[i]) { @@ -247,7 +257,33 @@ open_device() { axis = C_right_trigger; break; + case ABS_HAT0X: + if (_dpad_left_button == -1) { + // Emulate D-Pad. + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } + axis = C_none; + break; + + case ABS_HAT0Y: + if (_dpad_up_button == -1) { + // Emulate D-Pad. + _dpad_y_axis = i; + _dpad_up_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } + axis = C_none; + break; + default: + if (device_cat.is_debug()) { + device_cat.debug() << "Unmapped /dev/input/js" << _index + << " axis " << (int)i << ": 0x" << hex << (int)axmap[i] << "\n"; + } axis = C_none; break; } @@ -362,6 +398,14 @@ process_events() { set_button_state(index, (events[i].value != 0)); } else if (events[i].type & JS_EVENT_AXIS) { + if (index == _dpad_x_axis) { + set_button_state(_dpad_left_button, events[i].value < -1000); + set_button_state(_dpad_left_button+1, events[i].value > 1000); + } else if (index == _dpad_y_axis) { + set_button_state(_dpad_up_button, events[i].value < -1000); + set_button_state(_dpad_up_button+1, events[i].value > 1000); + } + ControlAxis axis = _controls[index]._axis; if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index 0ec90919f8..6507d4d80a 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -39,6 +39,12 @@ private: int _fd; int _index; + // These are used for D-pad emulation. + int _dpad_x_axis; + int _dpad_y_axis; + int _dpad_left_button; + int _dpad_up_button; + public: static TypeHandle get_class_type() { return _type_handle; From fd16e346609bdff1d3ee17fd2f142e85b8fe0bf3 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Jun 2016 13:30:57 +0200 Subject: [PATCH 014/424] Fix Xbox One controller on Windows, add untested support for flight sticks and steering wheels --- panda/src/device/evdevInputDevice.cxx | 91 +++++++++++++---- panda/src/device/inputDevice.cxx | 20 +++- panda/src/device/inputDevice.h | 12 +++ panda/src/device/linuxJoystickDevice.cxx | 32 +++++- panda/src/device/xInputDevice.cxx | 125 +++++++++++++++++------ 5 files changed, 225 insertions(+), 55 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index ef318c3e5d..4aa94e023d 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -23,11 +23,54 @@ #include #include -#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7))) +#define test_bit(bit, array) ((array)[(bit)>>3] & (1<<((bit)&7))) static InputDevice::ControlAxis axis_map[] = { - InputDevice::C_left_x, InputDevice::C_left_y, InputDevice::C_left_trigger, - InputDevice::C_right_x, InputDevice::C_right_y, InputDevice::C_right_trigger + // ABS_X = 0x00 + InputDevice::C_left_x, + // ABS_Y = 0x01 + InputDevice::C_left_y, + // ABS_Z = 0x02 + InputDevice::C_left_trigger, + // ABS_RX = 0x03 + InputDevice::C_right_x, + // ABS_RY = 0x04 + InputDevice::C_right_y, + // ABS_RZ = 0x05 + InputDevice::C_right_trigger, + // ABS_THROTTLE = 0x06 + InputDevice::C_throttle, + // ABS_RUDDER = 0x07 + InputDevice::C_rudder, + // ABS_WHEEL = 0x08 + InputDevice::C_wheel, + // ABS_GAS = 0x09 + InputDevice::C_accelerator, + // ABS_BRAKE = 0x0a + InputDevice::C_brake, + + InputDevice::C_none, + InputDevice::C_none, + InputDevice::C_none, + InputDevice::C_none, + InputDevice::C_none, + + // ABS_HAT0X = 0x10 + InputDevice::C_hat_x, + // ABS_HAT0Y = 0x11 + InputDevice::C_hat_y, + + // ABS_HAT1X = 0x12 + // ABS_HAT1Y = 0x13 + // ABS_HAT2X = 0x14 + // ABS_HAT2Y = 0x15 + // ABS_HAT3X = 0x16 + // ABS_HAT3Y = 0x17 + // ABS_PRESSURE = 0x18 + // ABS_DISTANCE = 0x19 + // ABS_TILT_X = 0x1a + // ABS_TILT_Y = 0x1b + // ABS_TOOL_WIDTH = 0x1c }; TypeHandle EvdevInputDevice::_type_handle; @@ -189,7 +232,7 @@ init_device() { } bool all_values_zero = true; - bool have_dpad_buttons = false; + bool emulate_dpad = true; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. @@ -213,7 +256,7 @@ init_device() { _buttons[bi]._state = S_up; } if (_buttons[bi]._handle == GamepadButton::dpad_left()) { - have_dpad_buttons = true; + emulate_dpad = false; } ++bi; } @@ -241,6 +284,10 @@ init_device() { } } + if (_device_class != DC_gamepad) { + emulate_dpad = false; + } + if (test_bit(EV_ABS, evtypes)) { // Check which axes are on the device. uint8_t axes[(ABS_CNT + 7) >> 3]; @@ -254,7 +301,25 @@ init_device() { int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { - set_control_map(i, axis_map[i]); + // Emulate D-Pad buttons if necessary. + if (i > ABS_HAT0Y) { + set_control_map(i, C_none); + + } else if (i == ABS_HAT0X && emulate_dpad) { + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + + } else if (i == ABS_HAT0Y && emulate_dpad) { + _dpad_y_axis = i; + _dpad_up_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + + } else { + set_control_map(i, axis_map[i]); + } // Check the initial value and ranges. struct input_absinfo absinfo; @@ -284,20 +349,6 @@ init_device() { all_values_zero = false; } } - - // Emulate D-Pad buttons if necessary. - if (i == ABS_HAT0X && !have_dpad_buttons) { - _dpad_x_axis = i; - _dpad_left_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); - } - if (i == ABS_HAT0Y && !have_dpad_buttons) { - _dpad_y_axis = i; - _dpad_up_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); - } } } } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 3ee0f05dcb..04dfc4caae 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -170,9 +170,25 @@ set_button_state(int index, bool down) { _buttons.resize(index + 1, ButtonState()); } - _buttons[index]._state = down ? S_down : S_up; + State new_state = down ? S_down : S_up; + if (_buttons[index]._state == new_state) { + return; + } + _buttons[index]._state = new_state; ButtonHandle handle = _buttons[index]._handle; + + if (device_cat.is_spam()) { + device_cat.spam() + << "Changed button " << index; + + if (handle != ButtonHandle::none()) { + device_cat.spam(false) << " (" << handle << ")"; + } + + device_cat.spam(false) << " to " << (down ? "down" : "up") << "\n"; + } + if (handle != ButtonHandle::none()) { _button_events->add_event(ButtonEvent(handle, down ? ButtonEvent::T_down : ButtonEvent::T_up)); } @@ -192,7 +208,7 @@ set_control_state(int index, double state) { _controls.resize(index + 1, AnalogState()); } - if (device_cat.is_spam()) { + if (device_cat.is_spam() && _controls[index]._state != state) { device_cat.spam() << "Changed control " << index; diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 0df79c3c65..e2e62735ff 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -73,6 +73,10 @@ PUBLISHED: DC_flight_stick, DC_steering_wheel, + DC_dance_pad, + + // Head-mounted display. + DC_hmd, }; protected: @@ -101,6 +105,14 @@ PUBLISHED: C_y, C_trigger, C_throttle, + C_rudder, + C_hat_x, + C_hat_y, + + // Steering wheel / pedals + C_wheel, + C_accelerator, + C_brake, }; INLINE string get_name() const; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index ed4e7fa329..f6ab5c7438 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -257,26 +257,50 @@ open_device() { axis = C_right_trigger; break; + case ABS_THROTTLE: + axis = InputDevice::C_throttle; + break; + + case ABS_RUDDER: + axis = InputDevice::C_rudder; + break; + + case ABS_WHEEL: + axis = InputDevice::C_wheel; + break; + + case ABS_GAS: + axis = InputDevice::C_accelerator; + break; + + case ABS_BRAKE: + axis = InputDevice::C_brake; + break; + case ABS_HAT0X: - if (_dpad_left_button == -1) { + if (_dpad_left_button == -1 && _device_class == DC_gamepad) { // Emulate D-Pad. _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + axis = C_none; + } else { + axis = C_hat_x; } - axis = C_none; break; case ABS_HAT0Y: - if (_dpad_up_button == -1) { + if (_dpad_up_button == -1 && _device_class == DC_gamepad) { // Emulate D-Pad. _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + axis = C_none; + } else { + axis = C_hat_y; } - axis = C_none; break; default: diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 8650d1e89b..9fe3778f8c 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -28,11 +28,33 @@ #ifndef XINPUT_CAPS_FFB_SUPPORTED #define XINPUT_CAPS_FFB_SUPPORTED 0x0001 #endif +#ifndef XINPUT_CAPS_NO_NAVIGATION +#define XINPUT_CAPS_NO_NAVIGATION 0x0010 +#endif #ifndef BATTERY_DEVTYPE_GAMEPAD #define BATTERY_DEVTYPE_GAMEPAD 0x00 #endif +#ifndef XINPUT_DEVSUBTYPE_WHEEL +#define XINPUT_DEVSUBTYPE_WHEEL 0x02 +#endif +#ifndef XINPUT_DEVSUBTYPE_ARCADE_STICK +#define XINPUT_DEVSUBTYPE_ARCADE_STICK 0x03 +#endif +#ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK +#define XINPUT_DEVSUBTYPE_FLIGHT_STICK 0x04 +#endif +#ifndef XINPUT_DEVSUBTYPE_DANCE_PAD +#define XINPUT_DEVSUBTYPE_DANCE_PAD 0x05 +#endif +#ifndef XINPUT_DEVSUBTYPE_GUITAR +#define XINPUT_DEVSUBTYPE_GUITAR 0x06 +#endif +#ifndef XINPUT_DEVSUBTYPE_DRUM_KIT +#define XINPUT_DEVSUBTYPE_DRUM_KIT 0x08 +#endif + #ifndef BATTERY_TYPE_DISCONNECTED #define BATTERY_TYPE_DISCONNECTED 0x00 #endif @@ -96,29 +118,6 @@ XInputDevice(DWORD user_index) : _controls.resize(6); _buttons.resize(16); - set_control_map(0, C_left_trigger); - set_control_map(1, C_right_trigger); - set_control_map(2, C_left_x); - set_control_map(3, C_left_y); - set_control_map(4, C_right_x); - set_control_map(5, C_right_y); - - set_button_map(0, GamepadButton::dpad_up()); - set_button_map(1, GamepadButton::dpad_down()); - set_button_map(2, GamepadButton::dpad_left()); - set_button_map(3, GamepadButton::dpad_right()); - set_button_map(4, GamepadButton::start()); - set_button_map(5, GamepadButton::back()); - set_button_map(6, GamepadButton::lstick()); - set_button_map(7, GamepadButton::rstick()); - set_button_map(8, GamepadButton::lshoulder()); - set_button_map(9, GamepadButton::rshoulder()); - set_button_map(10, GamepadButton::guide()); - set_button_map(11, GamepadButton::action_a()); - set_button_map(12, GamepadButton::action_b()); - set_button_map(13, GamepadButton::action_x()); - set_button_map(14, GamepadButton::action_y()); - // Check if the device is connected. If so, initialize it. XINPUT_CAPABILITIES caps; XINPUT_STATE state; @@ -242,19 +241,87 @@ init_xinput() { */ void XInputDevice:: init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { - if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { + // It seems that the Xbox One controller is reported as having a DevType of + // zero, at least when I tested in with XInput 1.3 on Windows 7. + //if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { + + // For subtypes and mappings, see this page: + // https://msdn.microsoft.com/en-us/library/windows/desktop/hh405050.aspx + switch (caps.SubType) { + default: + case XINPUT_DEVSUBTYPE_GAMEPAD: _device_class = DC_gamepad; - } else { - _device_class = DC_unknown; + set_control_map(0, C_left_trigger); + set_control_map(1, C_right_trigger); + set_control_map(2, C_left_x); + set_control_map(3, C_left_y); + set_control_map(4, C_right_x); + set_control_map(5, C_right_y); + break; + + case XINPUT_DEVSUBTYPE_WHEEL: + _device_class = DC_steering_wheel; + set_control_map(0, C_brake); + set_control_map(1, C_accelerator); + set_control_map(2, C_wheel); + set_control_map(3, C_none); + set_control_map(4, C_none); + set_control_map(5, C_none); + break; + + case XINPUT_DEVSUBTYPE_FLIGHT_STICK: + _device_class = DC_flight_stick; + set_control_map(0, C_rudder); + set_control_map(1, C_throttle); + set_control_map(2, C_x); + set_control_map(3, C_y); + set_control_map(4, C_hat_x); + set_control_map(5, C_hat_y); + break; + + case XINPUT_DEVSUBTYPE_DANCE_PAD: + _device_class = DC_dance_pad; + set_control_map(0, C_none); + set_control_map(1, C_none); + set_control_map(2, C_none); + set_control_map(3, C_none); + set_control_map(4, C_none); + set_control_map(5, C_none); + break; } + if (caps.Flags & XINPUT_CAPS_NO_NAVIGATION) { + set_button_map(0, ButtonHandle::none()); + set_button_map(1, ButtonHandle::none()); + set_button_map(2, ButtonHandle::none()); + set_button_map(3, ButtonHandle::none()); + set_button_map(4, ButtonHandle::none()); + set_button_map(5, ButtonHandle::none()); + } else { + set_button_map(0, GamepadButton::dpad_up()); + set_button_map(1, GamepadButton::dpad_down()); + set_button_map(2, GamepadButton::dpad_left()); + set_button_map(3, GamepadButton::dpad_right()); + set_button_map(4, GamepadButton::start()); + set_button_map(5, GamepadButton::back()); + } + set_button_map(6, GamepadButton::lstick()); + set_button_map(7, GamepadButton::rstick()); + set_button_map(8, GamepadButton::lshoulder()); + set_button_map(9, GamepadButton::rshoulder()); + set_button_map(10, GamepadButton::guide()); + set_button_map(11, GamepadButton::action_a()); + set_button_map(12, GamepadButton::action_b()); + set_button_map(13, GamepadButton::action_x()); + set_button_map(14, GamepadButton::action_y()); + if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { _flags |= IDF_has_vibration; } if (get_battery_information != NULL) { XINPUT_BATTERY_INFORMATION batt; - if (get_battery_information(_index, 0, &batt) == ERROR_SUCCESS) { + if (get_battery_information(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) { if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED && batt.BatteryType != BATTERY_TYPE_WIRED) { // This device has a battery. Report the battery level. @@ -276,7 +343,7 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { { // Reformat the serial number into its original hex string form. char sn[10]; - sprintf(sn, "%08X", businfo.InstanceID); + sprintf_s(sn, 10, "%08X", businfo.InstanceID); _serial_number.assign(sn, 8); } @@ -284,7 +351,7 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { // first need to construct the device path. Fortunately, we now have // enough information to do so. char path[32]; - sprintf(path, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID); + sprintf_s(path, 32, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID); DEVINST inst; if (CM_Locate_DevNodeA(&inst, path, 0) != 0) { From c7dbf193f1ffb43c4257e8a9611a4cc4e12a9650 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 28 Jun 2016 17:49:47 +0200 Subject: [PATCH 015/424] Fix Linux crash with Xbox One controller, fix compile warnings --- panda/src/device/evdevInputDevice.cxx | 54 +++++++++++++----------- panda/src/device/linuxJoystickDevice.cxx | 31 ++++++++------ 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 4aa94e023d..bb91144d6a 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -181,7 +181,10 @@ do_set_vibration(double strong, double weak) { play.code = _ff_id; play.value = 1; - write(_fd, &play, sizeof(play)); + if (write(_fd, &play, sizeof(play)) < 0) { + device_cat.warning() + << "Failed to write force-feedback event: " << strerror(errno) << "\n"; + } } } @@ -301,22 +304,22 @@ init_device() { int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { - // Emulate D-Pad buttons if necessary. - if (i > ABS_HAT0Y) { + if (i >= ABS_HAT0X) { set_control_map(i, C_none); - } else if (i == ABS_HAT0X && emulate_dpad) { - _dpad_x_axis = i; - _dpad_left_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); - - } else if (i == ABS_HAT0Y && emulate_dpad) { - _dpad_y_axis = i; - _dpad_up_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + // Emulate D-Pad buttons if necessary. + if (i == ABS_HAT0X && emulate_dpad) { + _dpad_x_axis = i; + _dpad_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } else if (i == ABS_HAT0Y && emulate_dpad) { + _dpad_y_axis = i; + _dpad_up_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } } else { set_control_map(i, axis_map[i]); } @@ -379,27 +382,30 @@ init_device() { sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); FILE *f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - if (buffer[0] != 0) { - _name.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } } fclose(f); } sprintf(path, "/sys/class/input/event%d/device/device/../manufacturer", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - _manufacturer.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + } fclose(f); } sprintf(path, "/sys/class/input/event%d/device/device/../serial", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - _serial_number.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + } fclose(f); } diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index f6ab5c7438..8c3a91cb6b 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -319,40 +319,47 @@ open_device() { sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index); FILE *f = fopen(path, "r"); if (f) { - fscanf(f, "%hx", &_vendor_id); + if (fscanf(f, "%hx", &_vendor_id) < 1) { + _vendor_id = 0; + } fclose(f); } sprintf(path, "/sys/class/input/js%d/device/id/product", _index); f = fopen(path, "r"); if (f) { - fscanf(f, "%hx", &_product_id); + if (fscanf(f, "%hx", &_product_id) < 1) { + _product_id = 0; + } fclose(f); } char buffer[256]; sprintf(path, "/sys/class/input/js%d/device/device/../product", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - if (buffer[0] != 0) { - _name.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + if (buffer[0] != 0) { + _name.assign(buffer); + } } fclose(f); } sprintf(path, "/sys/class/input/js%d/device/device/../manufacturer", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - _manufacturer.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + _manufacturer.assign(buffer); + } fclose(f); } sprintf(path, "/sys/class/input/js%d/device/device/../serial", _index); f = fopen(path, "r"); if (f) { - fgets(buffer, sizeof(buffer), f); - buffer[strcspn(buffer, "\r\n")] = 0; - _serial_number.assign(buffer); + if (fgets(buffer, sizeof(buffer), f) != NULL) { + buffer[strcspn(buffer, "\r\n")] = 0; + _serial_number.assign(buffer); + } fclose(f); } From dcdbe4b339baf0134228787ecbb0f6495d1fc186 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 21 Jul 2016 12:44:44 +0200 Subject: [PATCH 016/424] Add handling for new gamepad buttons Added handles to 1, 2, next and previous as available on the wiimote --- panda/src/device/evdevInputDevice.cxx | 12 ++++++++++++ panda/src/device/linuxJoystickDevice.cxx | 16 ++++++++++++++++ panda/src/putil/gamepadButton.cxx | 12 ++++++++++++ panda/src/putil/gamepadButton.h | 6 ++++++ 4 files changed, 46 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index bb91144d6a..0bc4a8d9ff 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -693,6 +693,12 @@ map_button(int code) { case BTN_TR2: return GamepadButton::rtrigger(); + case BTN_1: + return GamepadButton::action_1(); + + case BTN_2: + return GamepadButton::action_2(); + case BTN_SELECT: return GamepadButton::back(); @@ -702,6 +708,12 @@ map_button(int code) { case BTN_MODE: return GamepadButton::guide(); + case KEY_NEXT: + return GamepadButton::next(); + + case KEY_PREVIOUS: + return GamepadButton::previous(); + case BTN_THUMBL: return GamepadButton::lstick(); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 8c3a91cb6b..b6b622412b 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -163,6 +163,14 @@ open_device() { handle = GamepadButton::rtrigger(); break; + case BTN_1: + handle = GamepadButton::action_1(); + break; + + case BTN_2: + handle = GamepadButton::action_2(); + break; + case BTN_SELECT: handle = GamepadButton::back(); break; @@ -175,6 +183,14 @@ open_device() { handle = GamepadButton::guide(); break; + case KEY_NEXT: + handle = GamepadButton::next(); + break; + + case KEY_PREVIOUS: + handle = GamepadButton::previous(); + break; + case BTN_THUMBL: handle = GamepadButton::lstick(); break; diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index fd6fa1b396..ba7fe41b5a 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -34,6 +34,9 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(back) DEFINE_GAMEPAD_BUTTON_HANDLE(guide) DEFINE_GAMEPAD_BUTTON_HANDLE(start) +DEFINE_GAMEPAD_BUTTON_HANDLE(next) +DEFINE_GAMEPAD_BUTTON_HANDLE(previous) + DEFINE_GAMEPAD_BUTTON_HANDLE(action_a) DEFINE_GAMEPAD_BUTTON_HANDLE(action_b) DEFINE_GAMEPAD_BUTTON_HANDLE(action_c) @@ -41,6 +44,9 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(action_x) DEFINE_GAMEPAD_BUTTON_HANDLE(action_y) DEFINE_GAMEPAD_BUTTON_HANDLE(action_z) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_1) +DEFINE_GAMEPAD_BUTTON_HANDLE(action_2) + /** * This is intended to be called only once, by the static initialization * performed in config_util.cxx. @@ -63,10 +69,16 @@ init_gamepad_buttons() { ButtonRegistry::ptr()->register_button(_guide, "guide"); ButtonRegistry::ptr()->register_button(_start, "start"); + ButtonRegistry::ptr()->register_button(_next, "next"); + ButtonRegistry::ptr()->register_button(_previous, "previous"); + ButtonRegistry::ptr()->register_button(_action_a, "action_a"); ButtonRegistry::ptr()->register_button(_action_b, "action_b"); ButtonRegistry::ptr()->register_button(_action_c, "action_c"); ButtonRegistry::ptr()->register_button(_action_x, "action_x"); ButtonRegistry::ptr()->register_button(_action_y, "action_y"); ButtonRegistry::ptr()->register_button(_action_z, "action_z"); + + ButtonRegistry::ptr()->register_button(_action_1, "action_1"); + ButtonRegistry::ptr()->register_button(_action_2, "action_2"); } diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index b41f494b71..9a8597a6b9 100644 --- a/panda/src/putil/gamepadButton.h +++ b/panda/src/putil/gamepadButton.h @@ -40,6 +40,9 @@ PUBLISHED: static ButtonHandle guide(); static ButtonHandle start(); + static ButtonHandle next(); + static ButtonHandle previous(); + static ButtonHandle action_a(); static ButtonHandle action_b(); static ButtonHandle action_c(); @@ -47,6 +50,9 @@ PUBLISHED: static ButtonHandle action_y(); static ButtonHandle action_z(); + static ButtonHandle action_1(); + static ButtonHandle action_2(); + public: static void init_gamepad_buttons(); }; From a6110a606eb56ebc5149a91acea23a2e10909cf8 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 21 Jul 2016 12:46:20 +0200 Subject: [PATCH 017/424] renamed gamepad to device in inputDeviceManager renamed as not every device is a gamepad --- panda/src/device/inputDeviceManager.cxx | 29 ++++++++++++++++++++----- panda/src/device/inputDeviceManager.h | 7 +++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index e4be920583..edcc2a7903 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -246,23 +246,40 @@ consider_add_js_device(int js_index) { #endif /** - * Description: Returns all currently connected gamepad devices. + * Description: Returns all currently connected devices. */ InputDeviceSet InputDeviceManager:: -get_gamepads() const { - InputDeviceSet gamepads; +get_devices() const { + InputDeviceSet devices; LightMutexHolder holder(_lock); for (size_t i = 0; i < _connected_devices.size(); ++i) { InputDevice *device = _connected_devices[i]; - if (device->get_device_class() == InputDevice::DC_gamepad) { - gamepads.add_device(device); + devices.add_device(device); + } + + return devices; +} + +/** + * Description: Returns all currently connected devices of the given device class. + */ +InputDeviceSet InputDeviceManager:: +get_devices(InputDevice::DeviceClass device_class) const { + InputDeviceSet devices; + LightMutexHolder holder(_lock); + + for (size_t i = 0; i < _connected_devices.size(); ++i) { + InputDevice *device = _connected_devices[i]; + if (device->get_device_class() == device_class) { + devices.add_device(device); } } - return gamepads; + return devices; } + /** * Called when a new device has been discovered. This may also be used to * register virtual devices. diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 1bb3c353cd..9459f65c0d 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -38,7 +38,8 @@ private: #endif public: - InputDeviceSet get_gamepads() const; + InputDeviceSet get_devices() const; + InputDeviceSet get_devices(InputDevice::DeviceClass device_class) const; PUBLISHED: void add_device(InputDevice *device); @@ -48,8 +49,8 @@ PUBLISHED: INLINE static InputDeviceManager *get_global_ptr(); - // The set of all currently connected gamepad devices. - MAKE_PROPERTY(gamepads, get_gamepads); + // The set of all currently connected devices. + MAKE_PROPERTY(devices, get_devices); private: LightMutex _lock; From ef7cc925c4b3178fd59976ff8ec1a3db93963af6 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 21 Jul 2016 23:09:09 +0200 Subject: [PATCH 018/424] Add data graph node for devices Add the new inputDeviceNode class to be used in the data graph Add config variable for low batery level --- panda/src/device/config_device.cxx | 3 + panda/src/device/config_device.h | 2 + panda/src/device/inputDeviceNode.cxx | 71 ++++++++++++++++++++++++ panda/src/device/inputDeviceNode.h | 66 ++++++++++++++++++++++ panda/src/device/p3device_composite1.cxx | 1 + 5 files changed, 143 insertions(+) create mode 100644 panda/src/device/inputDeviceNode.cxx create mode 100644 panda/src/device/inputDeviceNode.h diff --git a/panda/src/device/config_device.cxx b/panda/src/device/config_device.cxx index fdb9ab8c52..dc0c458f8c 100644 --- a/panda/src/device/config_device.cxx +++ b/panda/src/device/config_device.cxx @@ -36,6 +36,9 @@ NotifyCategoryDef(device, ""); ConfigVariableBool asynchronous_clients ("asynchronous-clients", true); +ConfigVariableInt low_battery_level +("low-battery-level", 15); + ConfigureFn(config_device) { init_libdevice(); } diff --git a/panda/src/device/config_device.h b/panda/src/device/config_device.h index 8a7b6542a7..a0e6aa8341 100644 --- a/panda/src/device/config_device.h +++ b/panda/src/device/config_device.h @@ -17,10 +17,12 @@ #include "pandabase.h" #include "notifyCategoryProxy.h" #include "configVariableBool.h" +#include "configVariableInt.h" NotifyCategoryDecl(device, EXPCL_PANDA_DEVICE, EXPTP_PANDA_DEVICE); extern ConfigVariableBool asynchronous_clients; +extern ConfigVariableInt low_battery_level; extern EXPCL_PANDA_DEVICE void init_libdevice(); diff --git a/panda/src/device/inputDeviceNode.cxx b/panda/src/device/inputDeviceNode.cxx new file mode 100644 index 0000000000..662b2dbac8 --- /dev/null +++ b/panda/src/device/inputDeviceNode.cxx @@ -0,0 +1,71 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file InputDeviceNode.cxx + * @author fireclaw + * @date 2016-07-14 + */ + +#include "config_device.h" +#include "inputDeviceNode.h" +#include "dataNodeTransmit.h" +#include "inputDeviceManager.h" + +TypeHandle InputDeviceNode::_type_handle; + +InputDeviceNode:: +InputDeviceNode(InputDevice *device, const string &name) : + DataNode(name), + _device(device) +{ + _button_events_output = define_output("button_events", ButtonEventList::get_class_type()); + _low_battery_event_output = define_output("low_battery_level_event", EventStoreInt::get_class_type()); +} + +/** + * Redirects the class to get the data from a different device. + */ +void InputDeviceNode:: +set_device(InputDevice *device) { + _device = device; +} + +/** + * Returns the associated device. + */ +PT(InputDevice) InputDeviceNode:: +get_device() const { + return _device; +} + +/** + * The virtual implementation of transmit_data(). This function receives an + * array of input parameters and should generate an array of output + * parameters. The input parameters may be accessed with the index numbers + * returned by the define_input() calls that were made earlier (presumably in + * the constructor); likewise, the output parameters should be set with the + * index numbers returned by the define_output() calls. + */ +void InputDeviceNode:: +do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, + DataNodeTransmit &output) { + // get all button events of the device and forward them to the data graph + if (_device->has_button_event()) { + PT(ButtonEventList) bel = _device->get_button_events(); + output.set_data(_button_events_output, EventParameter(bel)); + } + + // calculate the battery level in percent and set a warning if the level is to low + if (_device->has_battery()) { + short bl = _device->get_battery_level(); + short bl_percent = bl / (_device->get_max_battery_level() / (short)100); + if (bl_percent <= low_battery_level) { + output.set_data(_low_battery_event_output, EventParameter(1)); + } + } +} diff --git a/panda/src/device/inputDeviceNode.h b/panda/src/device/inputDeviceNode.h new file mode 100644 index 0000000000..a8e6baef57 --- /dev/null +++ b/panda/src/device/inputDeviceNode.h @@ -0,0 +1,66 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file InputDeviceNode.h + * @author fireclaw + * @date 2016-07-14 + */ + +#ifndef INPUTDEVICENODE_H +#define INPUTDEVICENODE_H + +#include "pandabase.h" + +#include "dataNode.h" +#include "inputDeviceManager.h" +#include "linmath_events.h" + +/** + * Reads the controler data sent from the InputDeviceManager, and + * transmits it down the data graph. + * + * + */ +class EXPCL_PANDA_DEVICE InputDeviceNode : public DataNode { +PUBLISHED: + InputDeviceNode(InputDevice *device, const string &name); + void set_device(InputDevice *device); + PT(InputDevice) get_device() const; + +protected: + // Inherited from DataNode + virtual void do_transmit_data(DataGraphTraverser *trav, + const DataNodeTransmit &input, + DataNodeTransmit &output); + +private: + // outputs + int _button_events_output; + int _low_battery_event_output; + + PT(InputDevice) _device; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + DataNode::init_type(); + register_type(_type_handle, "InputDeviceNode", + DataNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#endif // INPUTDEVICENODE_H diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 2e541a6f47..90c0b54133 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -10,3 +10,4 @@ #include "inputDeviceManager.cxx" #include "inputDeviceSet.cxx" #include "linuxJoystickDevice.cxx" +#include "inputDeviceNode.cxx" From 2e8338c681a4642fba04f35b60ba9f1bd73cd660 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 21 Jul 2016 23:12:07 +0200 Subject: [PATCH 019/424] Add the new inputDeviceNode to the data graph create node and add to data graph for any connected device add function to add and remove devices and call them on dis-/connection add poll call for all devices in the __dataLoop task --- direct/src/showbase/ShowBase.py | 80 +++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 0916e83f7d..2900c7aeae 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -177,6 +177,7 @@ class ShowBase(DirectObject.DirectObject): self.trackball = None self.texmem = None self.showVertices = None + self.deviceButtonThrowers = [] ## This is a NodePath pointing to the Camera object set up for the 3D scene. ## This is usually a child of self.camera. @@ -301,6 +302,7 @@ class ShowBase(DirectObject.DirectObject): ## The global job manager, as imported from JobManagerGlobal. self.jobMgr = jobMgr + ## Particle manager self.particleMgr = None self.particleMgrEnabled = 0 @@ -313,6 +315,12 @@ class ShowBase(DirectObject.DirectObject): ## This is the global input device manager, which keeps track of ## connected input devices. self.devices = InputDeviceManager.getGlobalPtr() + # add existing devices to the data graph + for device in self.devices.devices: + self.connectDevice(device) + # Checks for device connection and disconnection + self.accept('connect-device', self.connectDevice) + self.accept('disconnect-device', self.disconnectDevice) self.createStats() @@ -1664,6 +1672,74 @@ class ShowBase(DirectObject.DirectObject): return self.mouseWatcherNode.getModifierButtons().isDown( KeyboardButton.meta()) + def connectDevice(self, device): + """ + This function will get called each time a new device got + connected and will add that new device to the data graph. + + Each device class will get a specific prefix for thrown events. Those + are currently as follow + + gamepad + flight_stick + steering_wheel + dance_pad + mouse + keyboard + unclassified_device + + In addition, the index of that device will appended to the prefix, + so for example if you hit the A button of the first connected gamepad + you will get an event like "gamepad0-action_a" the second gamepad will + then be catchable via "gamepad1-button_event" and so on. + Note, each device class will have a separate 0 based index, this way + you can have a gamepad0 as well as a steering_wheel0 and flight_stick0. + + All newly created button throwers will be stored in + the deviceButtonThrowers lsit + """ + idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.getName())) + prefix = "unclassified_device" + if device.getDeviceClass() == InputDevice.DC_gamepad: + prefix = "gamepad" + elif device.getDeviceClass() == InputDevice.DC_flight_stick: + prefix = "flight_stick" + elif device.getDeviceClass() == InputDevice.DC_steering_wheel: + prefix = "steering_wheel" + elif device.getDeviceClass() == InputDevice.DC_dance_pad: + prefix = "dance_pad" + elif device.getDeviceClass() == InputDevice.DC_mouse: + prefix = "mouse" + elif device.getDeviceClass() == InputDevice.DC_keyboard: + prefix = "keyboard" + + id = 0 + for dev in self.devices.devices: + if dev == device: + break + elif dev.getDeviceClass() == device.getDeviceClass(): + id += 1 + print "" + print "MAPPED", device, "AS", prefix, "SET CLASS", device.getDeviceClass() + print "" + bt = idn.attachNewNode(ButtonThrower(prefix)) + bt.node().setPrefix("{}{}-".format(prefix, id)) + self.deviceButtonThrowers.append(bt) + + def disconnectDevice(self, device): + """ + This function will get called each time a new device got + connected. It is then used to clean up the given device from the + data graph. + """ + print device.getName() + idn = self.dataRoot.find("**/{}".format(device.getName)) + for bt in self.deviceButtonThrowers.list(): + if bt.getName() == idn.getName(): + self.deviceButtonThrowers.remove(bt) + break + self.dataRoot.removeNode(idn) + def addAngularIntegrator(self): if not self.physicsMgrAngular: physics = importlib.import_module('panda3d.physics') @@ -1856,6 +1932,10 @@ class ShowBase(DirectObject.DirectObject): # Check if there were newly connected devices. self.devices.update() + # Poll all connected devices. + for device in self.devices.devices: + device.poll() + # traverse the data graph. This reads all the control # inputs (from the mouse and keyboard, for instance) and also # directly acts upon them (for instance, to move the avatar). From 271997b46219b12a8d707599d59bb7750e79e748 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Fri, 22 Jul 2016 21:25:27 +0200 Subject: [PATCH 020/424] Moved get_devices functions into PUBLISHED section --- panda/src/device/inputDeviceManager.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 9459f65c0d..9596a841d2 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -37,11 +37,10 @@ private: InputDevice *consider_add_js_device(int index); #endif -public: +PUBLISHED: InputDeviceSet get_devices() const; InputDeviceSet get_devices(InputDevice::DeviceClass device_class) const; -PUBLISHED: void add_device(InputDevice *device); void remove_device(InputDevice *device); From 29896a39a478dc256f93e3c8de0c0ae0cf2a2d8b Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Fri, 22 Jul 2016 21:33:41 +0200 Subject: [PATCH 021/424] cleanup and fixes fixed event prefix ID handling fixed removing of nodes of disconnected devices removed debug prints added more comments --- direct/src/showbase/ShowBase.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 2900c7aeae..b1e84d1aae 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -1713,17 +1713,20 @@ class ShowBase(DirectObject.DirectObject): elif device.getDeviceClass() == InputDevice.DC_keyboard: prefix = "keyboard" + currentPrefixes = [] + for np in self.dataRoot.findAllMatches("**/{}".format(prefix)): + bt = np.node() + currentPrefixes.append(bt.getPrefix()) + id = 0 - for dev in self.devices.devices: - if dev == device: - break - elif dev.getDeviceClass() == device.getDeviceClass(): - id += 1 - print "" - print "MAPPED", device, "AS", prefix, "SET CLASS", device.getDeviceClass() - print "" + # Find the next free ID for the newly connected device + while "{}{}-".format(prefix, id) in currentPrefixes: + id+=1 + # Setup the button thrower for that device and register it's event prefix bt = idn.attachNewNode(ButtonThrower(prefix)) + self.notify.debug("Registred event prefix {}{}-".format(prefix, id)) bt.node().setPrefix("{}{}-".format(prefix, id)) + # append the new button thrower to the list of device button throwers self.deviceButtonThrowers.append(bt) def disconnectDevice(self, device): @@ -1732,13 +1735,13 @@ class ShowBase(DirectObject.DirectObject): connected. It is then used to clean up the given device from the data graph. """ - print device.getName() - idn = self.dataRoot.find("**/{}".format(device.getName)) - for bt in self.deviceButtonThrowers.list(): + self.notify.debug("Disconnect device {}".format(device.getName())) + idn = self.dataRoot.find("**/{}".format(device.getName())) + for bt in list(self.deviceButtonThrowers): if bt.getName() == idn.getName(): self.deviceButtonThrowers.remove(bt) break - self.dataRoot.removeNode(idn) + idn.removeNode() def addAngularIntegrator(self): if not self.physicsMgrAngular: From a98cc38c90a28df553201b7e1f8947702b348ad4 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Fri, 22 Jul 2016 21:34:10 +0200 Subject: [PATCH 022/424] Added gamepad device sample script --- samples/gamepad/main.py | 116 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 samples/gamepad/main.py diff --git a/samples/gamepad/main.py b/samples/gamepad/main.py new file mode 100644 index 0000000000..189c4e0f16 --- /dev/null +++ b/samples/gamepad/main.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +''' +Demonstrate usage of gamepads and other input devices + +In this sample you can use a gamepad type device to control the camera and +show some messages on screen. Using the left stick on the controler will +move the camera where the right stick will rotate the camera. +''' + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 +from direct.gui.OnscreenText import OnscreenText + +loadPrcFileData("", "notify-level-device debug") + +class App(ShowBase): + def __init__(self): + ShowBase.__init__(self) + # print all events sent through the messenger + self.messenger.toggleVerbose() + + self.lblWarning = OnscreenText( + text = "No devices found", + fg=(1,0,0,1), + scale = .25) + self.lblWarning.hide() + + self.lblAction = OnscreenText( + text = "Action", + fg=(1,1,1,1), + scale = .15) + self.lblAction.hide() + + self.checkDevices() + + # Accept device dis-/connection events + # NOTE: catching the events here will overwrite the accept in showbase, hence + # we need to forward the event in the functions we set here! + self.accept("connect-device", self.connect) + self.accept("disconnect-device", self.disconnect) + + self.accept("escape", exit) + self.accept("gamepad0-start", exit) + self.accept("flight_stick0-start", exit) + + # Accept button events of the first connected gamepad + self.accept("gamepad0-action_a", self.doAction, extraArgs=[True, "Action"]) + self.accept("gamepad0-action_a-up", self.doAction, extraArgs=[False, "Release"]) + self.accept("gamepad0-action_b", self.doAction, extraArgs=[True, "Action 2"]) + self.accept("gamepad0-action_b-up", self.doAction, extraArgs=[False, "Release"]) + + self.environment = loader.loadModel("environment") + self.environment.reparentTo(render) + + # disable pandas default mouse-camera controls so we can handle the camera + # movements by ourself + self.disableMouse() + + self.taskMgr.add(self.moveTask, "movement update task") + + def connect(self, device): + # we need to forward the event to the connectDevice function of showbase + self.connectDevice(device) + # Now we can check for ourself + self.checkDevices() + + def disconnect(self, device): + # we need to forward the event to the disconnectDevice function of showbase + self.disconnectDevice(device) + # Now we can check for ourself + self.checkDevices() + + def checkDevices(self): + # check if we have gamepad devices connected + if self.devices.get_devices(InputDevice.DC_gamepad): + # we have at least one gamepad device + self.lblWarning.hide() + else: + # no devices connected + self.lblWarning.show() + + def doAction(self, showText, text): + if showText and self.lblAction.isHidden(): + self.lblAction.show() + else: + self.lblAction.hide() + + def moveTask(self, task): + dt = globalClock.getDt() + movementVec = Vec3() + + gamepads = base.devices.getDevices(InputDevice.DC_gamepad) + if len(gamepads) == 0: + # savety check + return task.cont + + # we will use the first found gamepad + for i in range(gamepads[0].getNumControls()): + if gamepads[0].getControlMap(i) == InputDevice.C_left_x: + movementVec.setX(gamepads[0].getControlState(i)) + elif gamepads[0].getControlMap(i) == InputDevice.C_left_y: + movementVec.setY(gamepads[0].getControlState(i)) + + if gamepads[0].getControlMap(i) == InputDevice.C_right_x: + base.camera.setH(base.camera, 100 * dt * (gamepads[0].getControlState(i))) + elif gamepads[0].getControlMap(i) == InputDevice.C_right_y: + base.camera.setP(base.camera, 100 * dt * (gamepads[0].getControlState(i))) + + # calculate movement + base.camera.setX(base.camera, 100 * dt * movementVec.getX()) + base.camera.setY(base.camera, 100 * dt * movementVec.getY()) + + return task.cont + +app = App() +app.run() From 670efb9e646b0cd17404b6d14fa754ce5d715f2d Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 3 Aug 2016 20:20:01 +0200 Subject: [PATCH 023/424] Extended device class heuristics Extended and partly restructured the way how the device class gets set Added DC_COUNT to the DeviceClass enum to simplify loop checks --- panda/src/device/evdevInputDevice.cxx | 137 ++++++++++++++++++++------ panda/src/device/inputDevice.h | 3 + 2 files changed, 108 insertions(+), 32 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 0bc4a8d9ff..bdc82df218 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -227,6 +227,7 @@ init_device() { } _name.assign(name); + //cerr << "##### Now initializing device " << name << "\n"; struct input_id id; if (ioctl(_fd, EVIOCGID, &id) >= 0) { @@ -237,12 +238,111 @@ init_device() { bool all_values_zero = true; bool emulate_dpad = true; + bool has_keys = false; + bool has_axes = false; + + uint8_t keys[(KEY_CNT + 7) >> 3]; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. - uint8_t keys[(KEY_CNT + 7) >> 3]; memset(keys, 0, sizeof(keys)); ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys); + has_keys = true; + if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) { + _flags |= IDF_has_keyboard; + } + } + + int num_bits = 0; + uint8_t axes[(ABS_CNT + 7) >> 3]; + if (test_bit(EV_ABS, evtypes)) { + // Check which axes are on the device. + memset(axes, 0, sizeof(axes)); + num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; + has_axes = true; + } + + + // Try to detect which type of device we have here + int device_scores[DC_COUNT]; + memset(device_scores, 0, sizeof(device_scores)); + + // Test for specific keys + if (test_bit(BTN_GAMEPAD, keys)) { + device_scores[DC_gamepad] += 5; + device_scores[DC_steering_wheel] += 5; + device_scores[DC_flight_stick] += 5; + } + + if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) { + device_scores[DC_steering_wheel] += 10; + } + if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) { + device_scores[DC_steering_wheel] += 10; + } + if (test_bit(BTN_JOYSTICK, keys)) { + device_scores[DC_flight_stick] += 10; + } + if (test_bit(BTN_MOUSE, keys)) { + device_scores[DC_mouse] += 20; + } + uint8_t unknown_keys[] = {KEY_POWER}; + for (int i = 0; i < 1; i++) { + if (test_bit(unknown_keys[i], keys)) { + if (unknown_keys[i] == KEY_POWER) { + } + device_scores[DC_unknown] += 20; + } + } + if (_flags & IDF_has_keyboard) { + device_scores[DC_keyboard] += 20; + } + + // Test for specific name tags + string lowercase_name = _name; + for(int x=0; x<_name.length(); x++) { + lowercase_name[x]=tolower(lowercase_name[x]); + } + if (lowercase_name.find("gamepad") != string::npos) { + device_scores[DC_gamepad] += 10; + } + if (lowercase_name.find("wheel") != string::npos) { + device_scores[DC_steering_wheel] += 10; + } + if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) { + device_scores[DC_mouse] += 10; + } + if (lowercase_name.find("keyboard") != string::npos) { + device_scores[DC_keyboard] += 10; + } + // List of lowercase names that occur in unknown devices + string unknown_names[] = {"video bus", "power button", "sleep button"}; + for(int i = 0; i < 3; i++) { + if (lowercase_name.find(unknown_names[i]) != string::npos) { + device_scores[DC_unknown] += 20; + } + } + + // Check which device type got the most points + bool highscore_found = false; + size_t highest_score = 0; + while (!highscore_found) { + highscore_found = true; + for (size_t i = 0; i < DC_COUNT; i++) { + if (device_scores[i] > highest_score) { + highest_score = device_scores[i]; + _device_class = (DeviceClass)i; + highscore_found = false; + } + } + } + //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; + + if (_device_class != DC_gamepad) { + emulate_dpad = false; + } + + if (has_keys) { // Also check whether the buttons are currently pressed. uint8_t states[(KEY_CNT + 7) >> 3]; memset(states, 0, sizeof(states)); @@ -252,6 +352,7 @@ init_device() { for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { set_button_map(bi, map_button(i)); + //cerr << "Button " << bi << " is mapped by the driver to " << i << "\n"; if (test_bit(i, states)) { _buttons[bi]._state = S_down; all_values_zero = false; @@ -264,44 +365,15 @@ init_device() { ++bi; } } - - if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) { - _flags |= IDF_has_keyboard; - } - - // Check device type. - if (test_bit(BTN_GAMEPAD, keys)) { - _device_class = DC_gamepad; - - } else if (test_bit(BTN_JOYSTICK, keys)) { - _device_class = DC_flight_stick; - - } else if (test_bit(BTN_MOUSE, keys)) { - _device_class = DC_mouse; - - } else if (test_bit(BTN_WHEEL, keys)) { - _device_class = DC_steering_wheel; - - } else if (_flags & IDF_has_keyboard) { - _device_class = DC_keyboard; - } } - if (_device_class != DC_gamepad) { - emulate_dpad = false; - } - - if (test_bit(EV_ABS, evtypes)) { - // Check which axes are on the device. - uint8_t axes[(ABS_CNT + 7) >> 3]; - memset(axes, 0, sizeof(axes)); - + if (has_axes) { AxisRange range; range._scale = 1.0; range._bias = 0.0; _axis_ranges.resize(ABS_CNT, range); - int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; + for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { if (i >= ABS_HAT0X) { @@ -322,6 +394,7 @@ init_device() { } } else { set_control_map(i, axis_map[i]); + //cerr << "Axis " << axis_map[i] << " is mapped by the driver to " << i << "\n"; } // Check the initial value and ranges. diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index e2e62735ff..3d90bc60dd 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -77,6 +77,9 @@ PUBLISHED: // Head-mounted display. DC_hmd, + + // Count of this enum, used for loops + DC_COUNT, }; protected: From cf63a6cccb65ddff0c7e985a4ba41a5cbc34cfcb Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 3 Aug 2016 20:21:08 +0200 Subject: [PATCH 024/424] Added another example showing usage of wheel devices --- samples/gamepad/steeringWheel.py | 139 +++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 samples/gamepad/steeringWheel.py diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py new file mode 100644 index 0000000000..6353f24188 --- /dev/null +++ b/samples/gamepad/steeringWheel.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +''' +Demonstrate usage of steering wheels + +In this sample you can use a wheel type device to control the camera and +show some messages on screen. You can acclerate forward using the +accleration pedal and slow down using the break pedal. +''' + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 +from direct.gui.OnscreenText import OnscreenText + +loadPrcFileData("", "notify-level-device debug") + +class App(ShowBase): + def __init__(self): + ShowBase.__init__(self) + # print all events sent through the messenger + self.messenger.toggleVerbose() + + self.lblWarning = OnscreenText( + text = "No devices found", + fg=(1,0,0,1), + scale = .25) + self.lblWarning.hide() + + self.lblAction = OnscreenText( + text = "Action", + fg=(1,1,1,1), + scale = .15) + self.lblAction.hide() + + self.checkDevices() + + self.currentMoveSpeed = 0.0 + self.maxAccleration = 28.0 + self.deaccleration = 10.0 + self.deaclerationBreak = 37.0 + self.maxSpeed = 80.0 + + # Accept device dis-/connection events + # NOTE: catching the events here will overwrite the accept in showbase, hence + # we need to forward the event in the functions we set here! + self.accept("connect-device", self.connect) + self.accept("disconnect-device", self.disconnect) + + self.accept("escape", exit) + self.accept("flight_stick0-start", exit) + + # Accept button events of the first connected steering wheel + self.accept("steering_wheel0-action_a", self.doAction, extraArgs=[True, "Action"]) + self.accept("steering_wheel0-action_a-up", self.doAction, extraArgs=[False, "Release"]) + + self.environment = loader.loadModel("environment") + self.environment.reparentTo(render) + + self.wheelCenter = 0 + wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) + if len(wheels) > 0: + for i in range(wheels[0].getNumControls()): + if wheels[0].getControlMap(i) == InputDevice.C_wheel: + self.wheelCenter = wheels[0].getControlState(i) + + # disable pandas default mouse-camera controls so we can handle the camera + # movements by ourself + self.disableMouse() + base.camera.setZ(2) + + self.taskMgr.add(self.moveTask, "movement update task") + + def connect(self, device): + # we need to forward the event to the connectDevice function of showbase + self.connectDevice(device) + # Now we can check for ourself + self.checkDevices() + + def disconnect(self, device): + # we need to forward the event to the disconnectDevice function of showbase + self.disconnectDevice(device) + # Now we can check for ourself + self.checkDevices() + + def checkDevices(self): + # check if we have wheel devices connected + if self.devices.get_devices(InputDevice.DC_steering_wheel): + # we have at least one steering wheel device + self.lblWarning.hide() + else: + # no devices connected + self.lblWarning.show() + + def doAction(self, showText, text): + if showText and self.lblAction.isHidden(): + self.lblAction.show() + else: + self.lblAction.hide() + + def moveTask(self, task): + dt = globalClock.getDt() + movementVec = Vec3() + + wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) + if len(wheels) == 0: + # savety check + return task.cont + + if self.currentMoveSpeed > 0: + self.currentMoveSpeed -= dt * self.deaccleration + if self.currentMoveSpeed < 0: + self.currentMoveSpeed = 0 + + # we will use the first found wheel + for i in range(wheels[0].getNumControls()): + + if wheels[0].getControlMap(i) == InputDevice.C_accelerator: + accleration = wheels[0].getControlState(i) * self.maxAccleration + self.currentMoveSpeed += dt * accleration + + if self.currentMoveSpeed > wheels[0].getControlState(i) * self.maxSpeed: + self.currentMoveSpeed -= dt * self.deaccleration + elif wheels[0].getControlMap(i) == InputDevice.C_brake: + deacleration = wheels[0].getControlState(i) * self.deaclerationBreak + self.currentMoveSpeed -= dt * deacleration + + elif self.currentMoveSpeed < 0: + self.currentMoveSpeed = 0 + + if wheels[0].getControlMap(i) == InputDevice.C_wheel: + rotation = self.wheelCenter - wheels[0].getControlState(i) + base.camera.setH(base.camera, 100 * dt * rotation) + + # calculate movement + base.camera.setY(base.camera, dt * self.currentMoveSpeed) + + return task.cont + +app = App() +app.run() From e1661111c29dc55cf55136c2042adaca783dcd98 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 4 Aug 2016 22:52:01 +0200 Subject: [PATCH 025/424] Exposed AnalogState and ButtonState Published Analog- and ButtonState to be accessible from python Added functions to find Controls and buttons to simplify access Refactored highscore search for setting the device class --- panda/src/device/evdevInputDevice.cxx | 21 +++--- panda/src/device/inputDevice.I | 92 ++++++++++++++++++++---- panda/src/device/inputDevice.cxx | 32 ++++----- panda/src/device/inputDevice.h | 23 ++++-- panda/src/device/linuxJoystickDevice.cxx | 8 +-- 5 files changed, 123 insertions(+), 53 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index bdc82df218..573042c5d2 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -324,16 +324,11 @@ init_device() { } // Check which device type got the most points - bool highscore_found = false; size_t highest_score = 0; - while (!highscore_found) { - highscore_found = true; - for (size_t i = 0; i < DC_COUNT; i++) { - if (device_scores[i] > highest_score) { - highest_score = device_scores[i]; - _device_class = (DeviceClass)i; - highscore_found = false; - } + for (size_t i = 0; i < DC_COUNT; i++) { + if (device_scores[i] > highest_score) { + highest_score = device_scores[i]; + _device_class = (DeviceClass)i; } } //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; @@ -354,12 +349,12 @@ init_device() { set_button_map(bi, map_button(i)); //cerr << "Button " << bi << " is mapped by the driver to " << i << "\n"; if (test_bit(i, states)) { - _buttons[bi]._state = S_down; + _buttons[bi].state = S_down; all_values_zero = false; } else { - _buttons[bi]._state = S_up; + _buttons[bi].state = S_up; } - if (_buttons[bi]._handle == GamepadButton::dpad_left()) { + if (_buttons[bi].handle == GamepadButton::dpad_left()) { emulate_dpad = false; } ++bi; @@ -419,7 +414,7 @@ init_device() { _axis_ranges[i]._scale = factor; _axis_ranges[i]._bias = bias; - _controls[i]._state = fma(absinfo.value, factor, bias); + _controls[i].state = fma(absinfo.value, factor, bias); if (absinfo.value != 0) { all_values_zero = false; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index e63930984b..ab30e9eb49 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -197,7 +197,7 @@ set_button_map(int index, ButtonHandle button) { _buttons.resize(index + 1, ButtonState()); } - _buttons[index]._handle = button; + _buttons[index].handle = button; } /** @@ -208,7 +208,7 @@ set_button_map(int index, ButtonHandle button) { INLINE ButtonHandle InputDevice:: get_button_map(int index) const { if (index >= 0 && index < (int)_buttons.size()) { - return _buttons[index]._handle; + return _buttons[index].handle; } else { return ButtonHandle::none(); } @@ -221,7 +221,7 @@ get_button_map(int index) const { INLINE bool InputDevice:: get_button_state(int index) const { if (index >= 0 && index < (int)_buttons.size()) { - return (_buttons[index]._state == S_down); + return (_buttons[index].state == S_down); } else { return false; } @@ -234,12 +234,43 @@ get_button_state(int index) const { INLINE bool InputDevice:: is_button_known(int index) const { if (index >= 0 && index < (int)_buttons.size()) { - return _buttons[index]._state != S_unknown; + return _buttons[index].state != S_unknown; } else { return false; } } +/** + * Returns the ButtonState that is set at the given index, or throw an assection + * if the index was not found in the list. + */ +INLINE InputDevice::ButtonState InputDevice:: +get_button(size_t index) const { + if (index >= 0 && index < (int)_buttons.size()) { + return _buttons[index]; + } else { + /*device_cat.error() + << "Index " << index<< " was not found in the controls list\n";*/ + nassertr(false, ButtonState()); + } +} + +/** + * Returns the first ButtonState found with the given axis, or throw an assection + * if the button handle was not found in the list. + */ +INLINE InputDevice::ButtonState InputDevice:: +find_button(ButtonHandle handle) const { + for (int i; i < (int)_buttons.size(); i++) { + if (_buttons[i].handle == handle) { + return _buttons[i]; + } + } + /*device_cat.error() + << "Axis " << axis << " was not found in the controls list\n";*/ + nassertr(false, ButtonState()); +} + /** * Returns the number of analog controls known to the InputDevice. This number * may change as more controls are discovered. @@ -264,7 +295,7 @@ set_control_map(int index, ControlAxis axis) { _controls.resize(index + 1, AnalogState()); } - _controls[index]._axis = axis; + _controls[index].axis = axis; } /** @@ -275,7 +306,7 @@ set_control_map(int index, ControlAxis axis) { INLINE InputDevice::ControlAxis InputDevice:: get_control_map(int index) const { if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._axis; + return _controls[index].axis; } else { return C_none; } @@ -289,12 +320,43 @@ get_control_map(int index) const { INLINE double InputDevice:: get_control_state(int index) const { if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._state; + return _controls[index].state; } else { return 0.0; } } +/** + * Returns the AnalogAxis that is set at the given index, or throw an assection + * if the index was not found in the list. + */ +INLINE InputDevice::AnalogState InputDevice:: +get_control(size_t index) const { + if (index >= 0 && index < (int)_controls.size()) { + return _controls[index]; + } else { + /*device_cat.error() + << "Index " << index<< " was not found in the controls list\n";*/ + nassertr(false, AnalogState()); + } +} + +/** + * Returns the first AnalogAxis found with the given axis, or throw an assection + * if the axis was not found in the list. + */ +INLINE InputDevice::AnalogState InputDevice:: +find_control(ControlAxis axis) const { + for (int i; i < (int)_controls.size(); i++) { + if (_controls[i].axis == axis) { + return _controls[i]; + } + } + /*device_cat.error() + << "Axis " << axis << " was not found in the controls list\n";*/ + nassertr(false, AnalogState()); +} + /** * Returns true if the state of the indicated analog control is known, or false * if we have never heard anything about this particular control. @@ -302,7 +364,7 @@ get_control_state(int index) const { INLINE bool InputDevice:: is_control_known(int index) const { if (index >= 0 && index < (int)_controls.size()) { - return _controls[index]._known; + return _controls[index].known; } else { return false; } @@ -377,8 +439,8 @@ operator < (const InputDevice &) const { */ INLINE InputDevice::ButtonState:: ButtonState() : - _handle(ButtonHandle::none()), - _state(S_unknown) + handle(ButtonHandle::none()), + state(S_unknown) { } @@ -387,8 +449,8 @@ ButtonState() : */ INLINE InputDevice::ButtonState:: ButtonState(ButtonHandle handle) : - _handle(handle), - _state(S_unknown) + handle(handle), + state(S_unknown) { } @@ -397,8 +459,8 @@ ButtonState(ButtonHandle handle) : */ INLINE InputDevice::AnalogState:: AnalogState() : - _axis(C_none), - _state(0.0), - _known(false) + axis(C_none), + state(0.0), + known(false) { } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 04dfc4caae..956a821bef 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -171,12 +171,12 @@ set_button_state(int index, bool down) { } State new_state = down ? S_down : S_up; - if (_buttons[index]._state == new_state) { + if (_buttons[index].state == new_state) { return; } - _buttons[index]._state = new_state; + _buttons[index].state = new_state; - ButtonHandle handle = _buttons[index]._handle; + ButtonHandle handle = _buttons[index].handle; if (device_cat.is_spam()) { device_cat.spam() @@ -208,19 +208,19 @@ set_control_state(int index, double state) { _controls.resize(index + 1, AnalogState()); } - if (device_cat.is_spam() && _controls[index]._state != state) { + if (device_cat.is_spam() && _controls[index].state != state) { device_cat.spam() << "Changed control " << index; - if (_controls[index]._axis != C_none) { - device_cat.spam(false) << " (" << _controls[index]._axis << ")"; + if (_controls[index].known != C_none) { + device_cat.spam(false) << " (" << _controls[index].known << ")"; } device_cat.spam(false) << " to " << state << "\n"; } - _controls[index]._state = state; - _controls[index]._known = true; + _controls[index].state = state; + _controls[index].known = true; } /** @@ -308,13 +308,13 @@ output_buttons(ostream &out) const { Buttons::const_iterator bi; for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { const ButtonState &state = (*bi); - if (state._state != S_unknown) { + if (state.state != S_unknown) { if (any_buttons) { out << ", "; } any_buttons = true; out << (int)(bi - _buttons.begin()) << "="; - if (state._state == S_up) { + if (state.state == S_up) { out << "up"; } else { out << "down"; @@ -336,17 +336,17 @@ write_buttons(ostream &out, int indent_level) const { Buttons::const_iterator bi; for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { const ButtonState &state = (*bi); - if (state._state != S_unknown) { + if (state.state != S_unknown) { any_buttons = true; indent(out, indent_level) << (int)(bi - _buttons.begin()) << ". "; - if (state._handle != ButtonHandle::none()) { - out << "(" << state._handle << ") "; + if (state.handle != ButtonHandle::none()) { + out << "(" << state.handle << ") "; } - if (state._state == S_up) { + if (state.state == S_up) { out << "up"; } else { out << "down"; @@ -372,11 +372,11 @@ write_controls(ostream &out, int indent_level) const { Controls::const_iterator ai; for (ai = _controls.begin(); ai != _controls.end(); ++ai) { const AnalogState &state = (*ai); - if (state._known) { + if (state.known) { any_controls = true; indent(out, indent_level) - << (int)(ai - _controls.begin()) << ". " << state._state << "\n"; + << (int)(ai - _controls.begin()) << ". " << state.state << "\n"; } } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 3d90bc60dd..f036394b92 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -250,13 +250,15 @@ public: S_down }; +PUBLISHED: class ButtonState { public: INLINE ButtonState(); INLINE ButtonState(ButtonHandle handle); - ButtonHandle _handle; - State _state; + PUBLISHED: + ButtonHandle handle; + State state; }; typedef pvector Buttons; Buttons _buttons; @@ -265,9 +267,10 @@ public: public: INLINE AnalogState(); - ControlAxis _axis; - double _state; - bool _known; + PUBLISHED: + ControlAxis axis; + double state; + bool known; }; typedef pvector Controls; Controls _controls; @@ -277,6 +280,16 @@ public: TrackerData _tracker_data; + + INLINE ButtonState get_button(size_t index) const; + INLINE ButtonState find_button(ButtonHandle handle) const; + + INLINE AnalogState get_control(size_t index) const; + INLINE AnalogState find_control(ControlAxis axis) const; + + // Make device controls iterable + MAKE_SEQ_PROPERTY(controls, get_num_controls, get_control); + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index b6b622412b..a1abddf3dd 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -225,7 +225,7 @@ open_device() { handle = ButtonHandle::none(); break; } - _buttons[i]._handle = handle; + _buttons[i].handle = handle; } } @@ -327,7 +327,7 @@ open_device() { axis = C_none; break; } - _controls[i]._axis = axis; + _controls[i].axis = axis; } } @@ -389,7 +389,7 @@ open_device() { // this gamepad yet (which means it hasn't been plugged in for this session) if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) { for (int i = 0; i < _controls.size(); ++i) { - if (_controls[i]._state != 0.0) { + if (_controls[i].state != 0.0) { _is_connected = true; return true; } @@ -453,7 +453,7 @@ process_events() { set_button_state(_dpad_up_button+1, events[i].value > 1000); } - ControlAxis axis = _controls[index]._axis; + ControlAxis axis = _controls[index].axis; if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { // We'd like to use 0.0 to indicate the resting position. From 2565f4b423b3d3c76c8258cae3495af15f1b9e07 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 4 Aug 2016 22:54:19 +0200 Subject: [PATCH 026/424] Renamed gamepad sample script to gamepad.py Renamed gamepad sample script Changed axis usage to the newly created functions and exposed classes --- samples/gamepad/{main.py => gamepad.py} | 28 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) rename samples/gamepad/{main.py => gamepad.py} (73%) diff --git a/samples/gamepad/main.py b/samples/gamepad/gamepad.py similarity index 73% rename from samples/gamepad/main.py rename to samples/gamepad/gamepad.py index 189c4e0f16..fe6cbd296f 100644 --- a/samples/gamepad/main.py +++ b/samples/gamepad/gamepad.py @@ -56,6 +56,16 @@ class App(ShowBase): # movements by ourself self.disableMouse() + # set the center position of the control sticks + # NOTE: here we assume, that the wheel is centered when the application get started. + # In real world applications, you should notice the user and give him enough time + # to center the wheel until you store the center position of the controler! + gamepads = base.devices.getDevices(InputDevice.DC_gamepad) + self.lxcenter = gamepads[0].findControl(InputDevice.C_left_x).state + self.lycenter = gamepads[0].findControl(InputDevice.C_left_y).state + self.rxcenter = gamepads[0].findControl(InputDevice.C_right_x).state + self.rycenter = gamepads[0].findControl(InputDevice.C_right_y).state + self.taskMgr.add(self.moveTask, "movement update task") def connect(self, device): @@ -95,16 +105,14 @@ class App(ShowBase): return task.cont # we will use the first found gamepad - for i in range(gamepads[0].getNumControls()): - if gamepads[0].getControlMap(i) == InputDevice.C_left_x: - movementVec.setX(gamepads[0].getControlState(i)) - elif gamepads[0].getControlMap(i) == InputDevice.C_left_y: - movementVec.setY(gamepads[0].getControlState(i)) - - if gamepads[0].getControlMap(i) == InputDevice.C_right_x: - base.camera.setH(base.camera, 100 * dt * (gamepads[0].getControlState(i))) - elif gamepads[0].getControlMap(i) == InputDevice.C_right_y: - base.camera.setP(base.camera, 100 * dt * (gamepads[0].getControlState(i))) + # Move the camera left/right + movementVec.setX(gamepads[0].findControl(InputDevice.C_left_x).state - self.lxcenter) + # Move the camera forward/backward + movementVec.setY(gamepads[0].findControl(InputDevice.C_left_y).state - self.lycenter) + # Control the cameras heading + base.camera.setH(base.camera, 100 * dt * (gamepads[0].findControl(InputDevice.C_right_x).state - self.rxcenter)) + # Control the cameras pitch + base.camera.setP(base.camera, 100 * dt * (gamepads[0].findControl(InputDevice.C_right_y).state - self.rycenter)) # calculate movement base.camera.setX(base.camera, 100 * dt * movementVec.getX()) From 3da928a4de888aa49ca3cdc914ccc2e34f7a15f2 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 4 Aug 2016 22:55:39 +0200 Subject: [PATCH 027/424] changed axis state detection to new functions and exposed state classes --- samples/gamepad/steeringWheel.py | 37 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py index 6353f24188..256c2e1eb3 100644 --- a/samples/gamepad/steeringWheel.py +++ b/samples/gamepad/steeringWheel.py @@ -55,12 +55,14 @@ class App(ShowBase): self.environment = loader.loadModel("environment") self.environment.reparentTo(render) + # save the center position of the wheel + # NOTE: here we assume, that the wheel is centered when the application get started. + # In real world applications, you should notice the user and give him enough time + # to center the wheel until you store the center position of the controler! self.wheelCenter = 0 wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) if len(wheels) > 0: - for i in range(wheels[0].getNumControls()): - if wheels[0].getControlMap(i) == InputDevice.C_wheel: - self.wheelCenter = wheels[0].getControlState(i) + self.wheelCenter = wheels[0].findControl(InputDevice.C_wheel).state # disable pandas default mouse-camera controls so we can handle the camera # movements by ourself @@ -111,24 +113,21 @@ class App(ShowBase): self.currentMoveSpeed = 0 # we will use the first found wheel - for i in range(wheels[0].getNumControls()): + # Acclerate + accleration = wheels[0].findControl(InputDevice.C_accelerator).state * self.maxAccleration + if self.currentMoveSpeed > wheels[0].findControl(InputDevice.C_accelerator).state * self.maxSpeed: + self.currentMoveSpeed -= dt * self.deaccleration + self.currentMoveSpeed += dt * accleration - if wheels[0].getControlMap(i) == InputDevice.C_accelerator: - accleration = wheels[0].getControlState(i) * self.maxAccleration - self.currentMoveSpeed += dt * accleration + # Break + deacleration = wheels[0].findControl(InputDevice.C_brake).state * self.deaclerationBreak + self.currentMoveSpeed -= dt * deacleration + if self.currentMoveSpeed < 0: + self.currentMoveSpeed = 0 - if self.currentMoveSpeed > wheels[0].getControlState(i) * self.maxSpeed: - self.currentMoveSpeed -= dt * self.deaccleration - elif wheels[0].getControlMap(i) == InputDevice.C_brake: - deacleration = wheels[0].getControlState(i) * self.deaclerationBreak - self.currentMoveSpeed -= dt * deacleration - - elif self.currentMoveSpeed < 0: - self.currentMoveSpeed = 0 - - if wheels[0].getControlMap(i) == InputDevice.C_wheel: - rotation = self.wheelCenter - wheels[0].getControlState(i) - base.camera.setH(base.camera, 100 * dt * rotation) + # Steering + rotation = self.wheelCenter - wheels[0].findControl(InputDevice.C_wheel).state + base.camera.setH(base.camera, 100 * dt * rotation) # calculate movement base.camera.setY(base.camera, dt * self.currentMoveSpeed) From 5a378becfc35f01f7d6182bb05fbcfc0ed994847 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 13:11:24 +0200 Subject: [PATCH 028/424] Added button mapping GUI --- samples/gamepad/mappingGUI.py | 342 ++++++++++++++++++++++ samples/gamepad/models/button_map.egg | 117 ++++++++ samples/gamepad/models/click.png | Bin 0 -> 30591 bytes samples/gamepad/models/dec_click.png | Bin 0 -> 847 bytes samples/gamepad/models/dec_disabled.png | Bin 0 -> 852 bytes samples/gamepad/models/dec_hover.png | Bin 0 -> 831 bytes samples/gamepad/models/dec_map.egg | 117 ++++++++ samples/gamepad/models/dec_ready.png | Bin 0 -> 781 bytes samples/gamepad/models/dialog.png | Bin 0 -> 4325 bytes samples/gamepad/models/disabled.png | Bin 0 -> 23853 bytes samples/gamepad/models/hover.png | Bin 0 -> 27590 bytes samples/gamepad/models/inc_click.png | Bin 0 -> 843 bytes samples/gamepad/models/inc_disabled.png | Bin 0 -> 866 bytes samples/gamepad/models/inc_hover.png | Bin 0 -> 835 bytes samples/gamepad/models/inc_map.egg | 117 ++++++++ samples/gamepad/models/inc_ready.png | Bin 0 -> 794 bytes samples/gamepad/models/li_ready_even.png | Bin 0 -> 186 bytes samples/gamepad/models/li_ready_odd.png | Bin 0 -> 186 bytes samples/gamepad/models/list_item_even.egg | 33 +++ samples/gamepad/models/list_item_odd.egg | 33 +++ samples/gamepad/models/ready.png | Bin 0 -> 25376 bytes samples/gamepad/models/thumb_click.png | Bin 0 -> 916 bytes samples/gamepad/models/thumb_disabled.png | Bin 0 -> 912 bytes samples/gamepad/models/thumb_hover.png | Bin 0 -> 892 bytes samples/gamepad/models/thumb_map.egg | 117 ++++++++ samples/gamepad/models/thumb_ready.png | Bin 0 -> 815 bytes 26 files changed, 876 insertions(+) create mode 100644 samples/gamepad/mappingGUI.py create mode 100644 samples/gamepad/models/button_map.egg create mode 100644 samples/gamepad/models/click.png create mode 100644 samples/gamepad/models/dec_click.png create mode 100644 samples/gamepad/models/dec_disabled.png create mode 100644 samples/gamepad/models/dec_hover.png create mode 100644 samples/gamepad/models/dec_map.egg create mode 100644 samples/gamepad/models/dec_ready.png create mode 100644 samples/gamepad/models/dialog.png create mode 100644 samples/gamepad/models/disabled.png create mode 100644 samples/gamepad/models/hover.png create mode 100644 samples/gamepad/models/inc_click.png create mode 100644 samples/gamepad/models/inc_disabled.png create mode 100644 samples/gamepad/models/inc_hover.png create mode 100644 samples/gamepad/models/inc_map.egg create mode 100644 samples/gamepad/models/inc_ready.png create mode 100644 samples/gamepad/models/li_ready_even.png create mode 100644 samples/gamepad/models/li_ready_odd.png create mode 100644 samples/gamepad/models/list_item_even.egg create mode 100644 samples/gamepad/models/list_item_odd.egg create mode 100644 samples/gamepad/models/ready.png create mode 100644 samples/gamepad/models/thumb_click.png create mode 100644 samples/gamepad/models/thumb_disabled.png create mode 100644 samples/gamepad/models/thumb_hover.png create mode 100644 samples/gamepad/models/thumb_map.egg create mode 100644 samples/gamepad/models/thumb_ready.png diff --git a/samples/gamepad/mappingGUI.py b/samples/gamepad/mappingGUI.py new file mode 100644 index 0000000000..ce9234bd1d --- /dev/null +++ b/samples/gamepad/mappingGUI.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python +''' +Demonstrate how a simple button mapping gui can be written +''' + +from direct.showbase.ShowBase import ShowBase +from direct.gui.DirectGui import ( + DGG, + DirectFrame, + DirectButton, + DirectLabel, + OkCancelDialog, + DirectScrolledFrame) +from panda3d.core import ( + VBase4, + TextNode, + Vec2, + InputDevice, + loadPrcFileData) + +# Make sure the textures look crisp on every device that supports +# non-power-2 textures +loadPrcFileData("", "textures-auto-power-2 #t") + +class App(ShowBase): + def __init__(self): + ShowBase.__init__(self) + + self.setBackgroundColor(0, 0, 0) + # make the font look nice at a big scale + DGG.getDefaultFont().setPixelsPerUnit(100) + + # a dict of actions and button/axis events + self.gamepadMapping = { + "Move forward":"Left Stick Y", + "Move backward":"Left Stick Y", + "Move left":"Left Stick X", + "Move right":"Left Stick X", + "Jump":"a", + "Action":"b", + "Sprint":"x", + "Map":"y", + "action-1":"c", + "action-2":"d", + "action-3":"e", + "action-4":"f", + "action-5":"g", + "action-6":"h", + "action-7":"i", + "action-8":"j", + "action-9":"k", + "action-10":"l", + "action-11":"m", + } + # this will store the action that we want to remap + self.actionToMap = "" + # this will store the key/axis that we want to asign to an action + self.newActionKey = "" + # this will store the label that needs to be actualized in the list + self.actualizeLabel = None + + # The geometry for our basic buttons + maps = loader.loadModel("models/button_map") + self.buttonGeom = ( + maps.find("**/ready"), + maps.find("**/click"), + maps.find("**/hover"), + maps.find("**/disabled")) + + # Create the dialog that asks the user for input on a given + # action to map a key to. + DGG.setDefaultDialogGeom("models/dialog.png") + # setup a dialog to ask for device input + self.dlgInput = OkCancelDialog( + dialogName="dlg_device_input", + pos=(0, 0, 0.25), + text="Hit desired key:", + text_fg=VBase4(0.898, 0.839, 0.730, 1.0), + text_shadow=VBase4(0, 0, 0, 0.75), + text_shadowOffset=Vec2(0.05, 0.05), + text_scale=0.05, + text_align=TextNode.ACenter, + fadeScreen=0.65, + frameColor=VBase4(0.3, 0.3, 0.3, 1), + button_geom=self.buttonGeom, + button_scale=0.15, + button_text_scale=0.35, + button_text_align=TextNode.ALeft, + button_text_fg=VBase4(0.898, 0.839, 0.730, 1.0), + button_text_pos=Vec2(-0.9, -0.125), + button_relief=1, + button_pad=Vec2(0.01, 0.01), + button_frameColor=VBase4(0, 0, 0, 0), + button_frameSize=VBase4(-1.0, 1.0, -0.25, 0.25), + button_pressEffect=False, + command=self.closeDialog) + self.dlgInput.setTransparency(True) + self.dlgInput.configureDialog() + scale = self.dlgInput["image_scale"] + self.dlgInput["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0) + self.dlgInput["text_pos"] = (self.dlgInput["text_pos"][0], self.dlgInput["text_pos"][1] + 0.06) + self.dlgInput.hide() + + # create a sample title + self.textscale = 0.1 + self.title = DirectLabel( + scale=self.textscale, + pos=(base.a2dLeft + 0.05, 0.0, base.a2dTop - (self.textscale + 0.05)), + frameColor=VBase4(0, 0, 0, 0), + text="Button Mapping", + text_align=TextNode.ALeft, + text_fg=VBase4(1, 1, 1, 1), + text_shadow=VBase4(0, 0, 0, 0.75), + text_shadowOffset=Vec2(0.05, 0.05)) + self.title.setTransparency(1) + + # Set up the list of actions that we can map keys to + # create a frame that will create the scrollbars for us + # Load the models for the scrollbar elements + thumbMaps = loader.loadModel("models/thumb_map") + thumbGeom = ( + thumbMaps.find("**/thumb_ready"), + thumbMaps.find("**/thumb_click"), + thumbMaps.find("**/thumb_hover"), + thumbMaps.find("**/thumb_disabled")) + incMaps = loader.loadModel("models/inc_map") + incGeom = ( + incMaps.find("**/inc_ready"), + incMaps.find("**/inc_click"), + incMaps.find("**/inc_hover"), + incMaps.find("**/inc_disabled")) + decMaps = loader.loadModel("models/dec_map") + decGeom = ( + decMaps.find("**/dec_ready"), + decMaps.find("**/dec_click"), + decMaps.find("**/dec_hover"), + decMaps.find("**/dec_disabled")) + # create the scrolled frame that will hold our list + self.lstActionMap = DirectScrolledFrame( + # make the frame occupy the whole window + frameSize=VBase4(base.a2dLeft, base.a2dRight, 0.0, 1.55), + # make the canvas as big as the frame + canvasSize=VBase4(base.a2dLeft, base.a2dRight, 0.0, 0.0), + # set the frames color to white + frameColor=VBase4(0, 0, 0.25, 0.75), + pos=(0, 0, -0.8), + + verticalScroll_scrollSize=0.2, + verticalScroll_frameColor=VBase4(0.02, 0.02, 0.02, 1), + + verticalScroll_thumb_relief=1, + verticalScroll_thumb_geom=thumbGeom, + verticalScroll_thumb_pressEffect=False, + verticalScroll_thumb_frameColor=VBase4(0, 0, 0, 0), + + verticalScroll_incButton_relief=1, + verticalScroll_incButton_geom=incGeom, + verticalScroll_incButton_pressEffect=False, + verticalScroll_incButton_frameColor=VBase4(0, 0, 0, 0), + + verticalScroll_decButton_relief=1, + verticalScroll_decButton_geom=decGeom, + verticalScroll_decButton_pressEffect=False, + verticalScroll_decButton_frameColor=VBase4(0, 0, 0, 0),) + + # creat the list items + idx = 0 + self.listBGEven = base.loader.loadModel("models/list_item_even") + self.listBGOdd = base.loader.loadModel("models/list_item_odd") + for key, value in self.gamepadMapping.items(): + item = self.__makeListItem(key, key, value, idx) + item.reparentTo(self.lstActionMap.getCanvas()) + idx += 1 + + # recalculate the canvas size to set scrollbars if necesary + self.lstActionMap["canvasSize"] = ( + base.a2dLeft+0.05, base.a2dRight-0.05, + -(len(self.gamepadMapping.keys())*0.1), 0.09) + self.lstActionMap.setCanvasSize() + + def closeDialog(self, result): + self.dlgInput.hide() + if result == DGG.DIALOG_OK: + # map the event to the given action + self.gamepadMapping[self.actionToMap] = self.newActionKey + # actualize the label in the list that shows the current + # event for the action + self.actualizeLabel["text"] = self.newActionKey + + # cleanup + self.dlgInput["text"] ="Hit desired key:" + self.actionToMap = "" + self.newActionKey = "" + self.actualizeLabel = None + for bt in base.buttonThrowers: + bt.node().setButtonDownEvent("") + for bt in base.deviceButtonThrowers: + bt.node().setButtonDownEvent("") + taskMgr.remove("checkControls") + + def changeMapping(self, action, label): + # set the action that we want to map a new key to + self.actionToMap = action + # set the label that needs to be actualized + self.actualizeLabel = label + # show our dialog + self.dlgInput.show() + + # catch all button events + for bt in base.buttonThrowers: + bt.node().setButtonDownEvent("keyListenEvent") + for bt in base.deviceButtonThrowers: + bt.node().setButtonDownEvent("deviceListenEvent") + self.setKeyCalled = False + self.accept("keyListenEvent", self.setKey) + self.accept("deviceListenEvent", self.setDeviceKey) + + # As there are no events thrown for control changes, we set up + # a task to check if the controls got moved + # This list will help us for checking which controls got moved + self.controlStates = {None:{}} + # fill it with all available controls + for device in base.devices.get_devices(): + for ctrl in device.controls: + if device not in self.controlStates.keys(): + self.controlStates.update({device: {ctrl.axis: ctrl.state}}) + else: + self.controlStates[device].update({ctrl.axis: ctrl.state}) + # start the task + taskMgr.add(self.watchControls, "checkControls") + + def watchControls(self, task): + # move through all devices and all it's controls + for device in base.devices.get_devices(): + for ctrl in device.controls: + # if a control got changed more than the given puffer zone + if self.controlStates[device][ctrl.axis] + 0.2 < ctrl.state or \ + self.controlStates[device][ctrl.axis] - 0.2 > ctrl.state: + # set the current state in the dict + self.controlStates[device][ctrl.axis] = ctrl.state + # check which axis got moved + if ctrl.axis == InputDevice.C_left_x: + self.setKey("Left Stick X") + elif ctrl.axis == InputDevice.C_left_y: + self.setKey("Left Stick Y") + elif ctrl.axis == InputDevice.C_left_trigger: + self.setKey("Left Trigger") + elif ctrl.axis == InputDevice.C_right_x: + self.setKey("Right Stick X") + elif ctrl.axis == InputDevice.C_right_y: + self.setKey("Right Stick Y") + elif ctrl.axis == InputDevice.C_right_trigger: + self.setKey("Right Trigger") + elif ctrl.axis == InputDevice.C_x: + self.setKey("X") + elif ctrl.axis == InputDevice.C_y: + self.setKey("Y") + elif ctrl.axis == InputDevice.C_trigger: + self.setKey("Trigger") + elif ctrl.axis == InputDevice.C_throttle: + self.setKey("Throttle") + elif ctrl.axis == InputDevice.C_rudder: + self.setKey("Rudder") + elif ctrl.axis == InputDevice.C_hat_x: + self.setKey("Hat X") + elif ctrl.axis == InputDevice.C_hat_y: + self.setKey("Hat Y") + elif ctrl.axis == InputDevice.C_wheel: + self.setKey("Wheel") + elif ctrl.axis == InputDevice.C_accelerator: + self.setKey("Acclerator") + elif ctrl.axis == InputDevice.C_brake: + self.setKey("Break") + return task.cont + + def setKey(self, args): + self.setKeyCalled = True + if self.dlgInput.buttonList[0].guiItem.getState() == 1: + # this occurs if the OK button was clicked. To prevent to + # always set the mouse1 event whenever the OK button was + # pressed, we instantly return from this function + return + self.dlgInput["text"] = "New event will be:\n\n" + args + self.newActionKey = args + + def setDeviceKey(self, args): + if not self.setKeyCalled: + self.setKey(args) + self.setKeyCalled = False + + def __makeListItem(self, itemName, action, event, index): + def dummy(): pass + if index % 2 == 0: + bg = self.listBGEven + else: + bg = self.listBGOdd + item = DirectFrame( + text=itemName, + geom=bg, + geom_scale=(base.a2dRight-0.05, 1, 0.1), + frameSize=VBase4(base.a2dLeft+0.05, base.a2dRight-0.05, -0.05, 0.05), + frameColor=VBase4(1,0,0,0), + text_align=TextNode.ALeft, + text_scale=0.05, + text_fg=VBase4(1,1,1,1), + text_pos=(base.a2dLeft + 0.3, -0.015), + text_shadow=VBase4(0, 0, 0, 0.35), + text_shadowOffset=Vec2(-0.05, -0.05), + pos=(0.05, 0, -(0.10 * index))) + item.setTransparency(True) + lbl = DirectLabel( + text=event, + text_fg=VBase4(1, 1, 1, 1), + text_scale=0.05, + text_pos=Vec2(0, -0.015), + frameColor=VBase4(0, 0, 0, 0), + ) + lbl.reparentTo(item) + lbl.setTransparency(True) + buttonScale = 0.15 + btn = DirectButton( + text="Change", + geom=self.buttonGeom, + scale=buttonScale, + text_scale=0.25, + text_align=TextNode.ALeft, + text_fg=VBase4(0.898, 0.839, 0.730, 1.0), + text_pos=Vec2(-0.9, -0.085), + relief=1, + pad=Vec2(0.01, 0.01), + frameColor=VBase4(0, 0, 0, 0), + frameSize=VBase4(-1.0, 1.0, -0.25, 0.25), + pos=(base.a2dRight-(0.898*buttonScale+0.3), 0, 0), + pressEffect=False, + command=self.changeMapping, + extraArgs=[action, lbl]) + btn.setTransparency(True) + btn.reparentTo(item) + return item + +app = App() +app.run() diff --git a/samples/gamepad/models/button_map.egg b/samples/gamepad/models/button_map.egg new file mode 100644 index 0000000000..75a305e0da --- /dev/null +++ b/samples/gamepad/models/button_map.egg @@ -0,0 +1,117 @@ + { + "egg-texture-cards -o button_map.egg -p 768,192 -g -1.0,1.0,-0.25,0.25 -wm clamp ready.png click.png hover.png disabled.png" +} + ready { + ready.png + wrap { clamp } +} + hover { + hover.png + wrap { clamp } +} + disabled { + disabled.png + wrap { clamp } +} + click { + click.png + wrap { clamp } +} + { + { 1 } + fps { 2 } + vpool { + 0 { + -1 0.25 0 + { 0 1 } + } + 1 { + -1 -0.25 0 + { 0 0 } + } + 2 { + 1 -0.25 0 + { 1 0 } + } + 3 { + 1 0.25 0 + { 1 1 } + } + 4 { + -1 0.25 0 + { 0 1 } + } + 5 { + -1 -0.25 0 + { 0 0 } + } + 6 { + 1 -0.25 0 + { 1 0 } + } + 7 { + 1 0.25 0 + { 1 1 } + } + 8 { + -1 0.25 0 + { 0 1 } + } + 9 { + -1 -0.25 0 + { 0 0 } + } + 10 { + 1 -0.25 0 + { 1 0 } + } + 11 { + 1 0.25 0 + { 1 1 } + } + 12 { + -1 0.25 0 + { 0 1 } + } + 13 { + -1 -0.25 0 + { 0 0 } + } + 14 { + 1 -0.25 0 + { 1 0 } + } + 15 { + 1 0.25 0 + { 1 1 } + } + } + ready { + { + { 1 1 1 1 } + { ready } + { 0 1 2 3 { vpool } } + } + } + click { + { + { 1 1 1 1 } + { click } + { 4 5 6 7 { vpool } } + } + } + hover { + { + { 1 1 1 1 } + { hover } + { 8 9 10 11 { vpool } } + } + } + disabled { + { + { 1 1 1 1 } + { disabled } + { 12 13 14 15 { vpool } } + } + } +} diff --git a/samples/gamepad/models/click.png b/samples/gamepad/models/click.png new file mode 100644 index 0000000000000000000000000000000000000000..fbd06a4df3bbc3d8e522a57cbed450ea9b2de610 GIT binary patch literal 30591 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 e?fGj10Dl3^n9EyoPuV>H0000e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00O5;L_t(o!^M|BNE>k!$G`9Q z(_E@Kjpko-F?U7~N}%BAC@7soK_Q2F;*o$R90;L@ zK&R7rKfoAU^u>wx%2Iy66yohq;J6L^?t~}5lq9k=qP?;Qvwz7LTcl#K_ys_m=<5t{ z^;gQuX_ToaYL$UxOv*PW0Ir#T;D}19lm~9qwU_e1^-2r?h_hRl!o9RdZO84q=ZoEU z2CKQEC`?fl77XCN2J2-n&1Umuv)Rn`@{eZ }e3R4Te@nnv&}gaEL+yPHd=)A3X) z6?Ysb+gAY2Ihm%Z`|leY8^++^AoUHvahz-}mmBimGnvebdcB_Q3jkw`*4EbahwGMQ z8H_QC9H3sWX9|VF@Wb_dK0maxvy+JwfQVpOmhqV1w(Y=K2n0~ARx=Y56T^@B^ZER+ zZQJR{0BqaVpNgr~Y6cO753sehl{O4ReJZY4EG9NLH&dYlRI62^+t}^x?a6Ml&=tV? z`ucdOR8qSI&~;rkO>;anfLg6)Jmb1fh61p)V%-;g@46Nz^J6&1Qi^_hwE!8xTSqan5J_aS2$~82k7-s?ki! z52wDC5aQ^+52qXe+U@p_L?ZEvh(?Gg+0FIraXkO*x^4>q{s9I*$&>iDcXj{(002ov JPDHLkV1fZKcS-;N literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/dec_map.egg b/samples/gamepad/models/dec_map.egg new file mode 100644 index 0000000000..71190d251a --- /dev/null +++ b/samples/gamepad/models/dec_map.egg @@ -0,0 +1,117 @@ + { + "egg-texture-cards -o dec_map.egg -p 32,32 -g -0.04,0.04,-0.04,0.04 -wm clamp dec_ready.png dec_click.png dec_hover.png dec_disabled.png" +} + dec_ready { + dec_ready.png + wrap { clamp } +} + dec_hover { + dec_hover.png + wrap { clamp } +} + dec_disabled { + dec_disabled.png + wrap { clamp } +} + dec_click { + dec_click.png + wrap { clamp } +} + { + { 1 } + fps { 2 } + vpool { + 0 { + -0.04 0.04 0 + { 0 1 } + } + 1 { + -0.04 -0.04 0 + { 0 0 } + } + 2 { + 0.04 -0.04 0 + { 1 0 } + } + 3 { + 0.04 0.04 0 + { 1 1 } + } + 4 { + -0.04 0.04 0 + { 0 1 } + } + 5 { + -0.04 -0.04 0 + { 0 0 } + } + 6 { + 0.04 -0.04 0 + { 1 0 } + } + 7 { + 0.04 0.04 0 + { 1 1 } + } + 8 { + -0.04 0.04 0 + { 0 1 } + } + 9 { + -0.04 -0.04 0 + { 0 0 } + } + 10 { + 0.04 -0.04 0 + { 1 0 } + } + 11 { + 0.04 0.04 0 + { 1 1 } + } + 12 { + -0.04 0.04 0 + { 0 1 } + } + 13 { + -0.04 -0.04 0 + { 0 0 } + } + 14 { + 0.04 -0.04 0 + { 1 0 } + } + 15 { + 0.04 0.04 0 + { 1 1 } + } + } + dec_ready { + { + { 1 1 1 1 } + { dec_ready } + { 0 1 2 3 { vpool } } + } + } + dec_click { + { + { 1 1 1 1 } + { dec_click } + { 4 5 6 7 { vpool } } + } + } + dec_hover { + { + { 1 1 1 1 } + { dec_hover } + { 8 9 10 11 { vpool } } + } + } + dec_disabled { + { + { 1 1 1 1 } + { dec_disabled } + { 12 13 14 15 { vpool } } + } + } +} diff --git a/samples/gamepad/models/dec_ready.png b/samples/gamepad/models/dec_ready.png new file mode 100644 index 0000000000000000000000000000000000000000..a097c1e5cb01030cdfa5cfc500539f58dccbcf39 GIT binary patch literal 781 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 literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/disabled.png b/samples/gamepad/models/disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..9b9dfe50146434aa74d780fc8573eda730b33fed GIT binary patch literal 23853 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

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

QL70(Y)*K0-AbW|YuPgfl4h|kwojQL70(Y)*K0-AbW|YuPgfl4h|kQ)~UZ@4g-ZGOI#yLobz*YQ}ap~oQqNuOHxx5 z$}>wc6x=<11Hv2m#DR*GJY5_^IIbrLgom$BPf7rSq=X#@4;(qL;2^`}OVb^;$uJnI WGV8yZX { + "egg-texture-cards -o list_item_even.egg -g -1,1,-0.5,0.5 -p 1,30 li_ready_even.png" +} + li_ready_even { + li_ready_even.png +} + { + vpool { + 0 { + -1 0.5 0 + { 0 1 } + } + 1 { + -1 -0.5 0 + { 0 0 } + } + 2 { + 1 -0.5 0 + { 1 0 } + } + 3 { + 1 0.5 0 + { 1 1 } + } + } + li_ready_even { + { + { 1 1 1 1 } + { li_ready_even } + { 0 1 2 3 { vpool } } + } + } +} diff --git a/samples/gamepad/models/list_item_odd.egg b/samples/gamepad/models/list_item_odd.egg new file mode 100644 index 0000000000..cde6c8642f --- /dev/null +++ b/samples/gamepad/models/list_item_odd.egg @@ -0,0 +1,33 @@ + { + "egg-texture-cards -o list_item_odd.egg -g -1,1,-0.5,0.5 -p 1,30 li_ready_odd.png" +} + li_ready_odd { + li_ready_odd.png +} + { + vpool { + 0 { + -1 0.5 0 + { 0 1 } + } + 1 { + -1 -0.5 0 + { 0 0 } + } + 2 { + 1 -0.5 0 + { 1 0 } + } + 3 { + 1 0.5 0 + { 1 1 } + } + } + li_ready_odd { + { + { 1 1 1 1 } + { li_ready_odd } + { 0 1 2 3 { vpool } } + } + } +} diff --git a/samples/gamepad/models/ready.png b/samples/gamepad/models/ready.png new file mode 100644 index 0000000000000000000000000000000000000000..e9cfed43da5a5c0d5429a376507d03d6daf739aa GIT binary patch literal 25376 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 Gu>S*DK+{M7 literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/thumb_click.png b/samples/gamepad/models/thumb_click.png new file mode 100644 index 0000000000000000000000000000000000000000..2fc059da828e9982bf2d5eff771f13daa6acd4c6 GIT binary patch literal 916 zcmeAS@N?(olHy`uVBq!ia0vp^3JeU4J2=>YEES8Vc|eLS$=lt9;Xep2*t>i(P=vF< zBeIx*fm;}a85w5HkpK#^mw5WRvOnP95Ytz0c-e3QC?r|p8d2h$pPQSSSHj?2l$uzQ znxasiS(2gP?&%v4-pD7;z`*p!)5S5Q;?~=1hThSE630G1xBV`(#PpHM*EG$g%~4#U zRu&dZc35>Z=iQaL^NUgDFT-7#H{vUr&px=c@WF!%3m06-vKDPob6Oz4(RAd=9@(Ji zR<7Q&SGoR|d%xRRS@(R-_dC*;&!5_~bEd$h$jvD}n;4&lynDM>;&$}bsHbyPW~xl} z`g~eZM69G&G&y^5)ZLiPI%5IBHywV|uAERZ-S+*h z+8Dq>o*E6=& zwSTuV+uND?T;krk=MMV3rBmZA~@v-<4;5j)Pk>)*IFaBbc?)k#-! z>i!v6{P-|?X4{#Zh)pKz6TW|o>scGV_10`&&tHrx<)>$UE9fc%1(Bz#pUXO@geCx2 C@=@&o literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/thumb_disabled.png b/samples/gamepad/models/thumb_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..8ad2805f10bb507d850686bed6c2afeed866a67f GIT binary patch literal 912 zcmeAS@N?(olHy`uVBq!ia0vp^3JeU4J2=>YEES8Vc|eLS$=lt9;Xep2*t>i(P=vF< zBeIx*fm;}a85w5HkpK#^mw5WRvOnP95YxAw^5RZAP)M@GHKN2hKQ}iuuY|$5C^fMp zHASI3vm`^o-P1Q9ypd0wfr06Vr;B4q#jUq@_h(Bb${hdLU-i)Gpb)1=Ws6c{0}J0u zv*@-4$-6B78}fe2TYalP5wweq>EKEiTUC^ zRo*@OCx5^5^7&Vr@3nsB$-jbCJySg`J-fTs^{MQg`o(Wj(!mGYmaKZW?VHNmh=XTV zPkLe@{nAM>zsFNG_qJEd+#5Ob%B6af&zEM`P5610Yg%mI=85YgaObbRVtA_3~AL_kUFVytXE$5WKxBcOo-4D0z73TlVoWA#3S5nv?g+ys4`Z}HAz+qM$ z9UZkre0%17ls1s)IX3f5mQj|2+U8j@JZ_IyH|$fXyyv<-BE~$B85nPmE5+|V*f4*> zmzYbFPac_DY{rOWW37_`UF^&3hB6YEES8Vc|eLS$=lt9;Xep2*t>i(P=vF< zBeIx*fm;}a85w5HkpK#^mw5WRvOnP95Ysnd2|p+X6p}1)jVN)>&&^HED`9XhN=+EaktaqI2fjo#6LBF8`8->GlH=VoNN=fQ(8CRX=$ zo~8?$N6wzCcd#leczI)~vwPvo7gKyWM7UVl+xcXfSWiB?v`2CB53%SIwzBgUIk8UO zw_J2zy4R-yStcd&Tj(U8Nl|OB<#8lN z_eU5W`&VIOx532Me6mWVx@TJcw54mWATgP zob_-2mL6pt8d*ce)`Vm z->>rAE(imIht6Qd+0V-+PoCVnfA77QC4KU&2L1t>4ZqX==^qFv`>`fD+kfL5nZwUZ zT$PmEE$fAE_~(@7tnG^Y*xZuLL1(u!oR{1%B}Bf` z{IS@B>mj!?LilfFh(wwM{an^LB{Ts5LDxGM literal 0 HcmV?d00001 diff --git a/samples/gamepad/models/thumb_map.egg b/samples/gamepad/models/thumb_map.egg new file mode 100644 index 0000000000..1b49062ef5 --- /dev/null +++ b/samples/gamepad/models/thumb_map.egg @@ -0,0 +1,117 @@ + { + "egg-texture-cards -o thumb_map.egg -p 32,440 -g -0.04,0.04,-0.55,0.55 -wm clamp thumb_ready.png thumb_click.png thumb_hover.png thumb_disabled.png" +} + thumb_ready { + thumb_ready.png + wrap { clamp } +} + thumb_hover { + thumb_hover.png + wrap { clamp } +} + thumb_disabled { + thumb_disabled.png + wrap { clamp } +} + thumb_click { + thumb_click.png + wrap { clamp } +} + { + { 1 } + fps { 2 } + vpool { + 0 { + -0.04 0.55 0 + { 0 1 } + } + 1 { + -0.04 -0.55 0 + { 0 0 } + } + 2 { + 0.04 -0.55 0 + { 1 0 } + } + 3 { + 0.04 0.55 0 + { 1 1 } + } + 4 { + -0.04 0.55 0 + { 0 1 } + } + 5 { + -0.04 -0.55 0 + { 0 0 } + } + 6 { + 0.04 -0.55 0 + { 1 0 } + } + 7 { + 0.04 0.55 0 + { 1 1 } + } + 8 { + -0.04 0.55 0 + { 0 1 } + } + 9 { + -0.04 -0.55 0 + { 0 0 } + } + 10 { + 0.04 -0.55 0 + { 1 0 } + } + 11 { + 0.04 0.55 0 + { 1 1 } + } + 12 { + -0.04 0.55 0 + { 0 1 } + } + 13 { + -0.04 -0.55 0 + { 0 0 } + } + 14 { + 0.04 -0.55 0 + { 1 0 } + } + 15 { + 0.04 0.55 0 + { 1 1 } + } + } + thumb_ready { + { + { 1 1 1 1 } + { thumb_ready } + { 0 1 2 3 { vpool } } + } + } + thumb_click { + { + { 1 1 1 1 } + { thumb_click } + { 4 5 6 7 { vpool } } + } + } + thumb_hover { + { + { 1 1 1 1 } + { thumb_hover } + { 8 9 10 11 { vpool } } + } + } + thumb_disabled { + { + { 1 1 1 1 } + { thumb_disabled } + { 12 13 14 15 { vpool } } + } + } +} diff --git a/samples/gamepad/models/thumb_ready.png b/samples/gamepad/models/thumb_ready.png new file mode 100644 index 0000000000000000000000000000000000000000..36a41ae5995a216062367d7eba0ab5752a9cfb4f GIT binary patch literal 815 zcmeAS@N?(olHy`uVBq!ia0vp^3JeU4J2=>YEES8Vc|eLS$=lt9;Xep2*t>i(P=vF< zBeIx*fm;}a85w5HkpK#^mw5WRvOnP95Yv~s(Rk)2P)M@GHKN2hKQ}iuuY|$5C^fMp zHASI3vm`^o-P1Q9ypd0wfq`kdr;B4q#jUq@_h$DaZ5=<^RfI~v7iRZN$vsDId4 zP=50HSDWv(dN(H}2bCr;uVB(n3D13C`aw>?+-e7J&c++J*vpO?+jA_w*|>vWJ$-`8 z%l;kmM#n0za4$|}pOsp%lP|ye!yDx-Y#-j*?B&d#wjhvGl3{(-0XM!1@pqLq@>TmE zoP3@W{_m{Sl>9@7%?x_fc+1yw>~6n#|JU8a6Ic&;nOGKT%TKd?oyF@Ewj$$K%B=5y zHqAQMvE!MLfP=?uMI-x}OhQy2}-F!MK97d$q%iEVb@ z{_wY^%-logRrWqOE3{_^cmL@Rf4GkvKcL=VRUqKDnYmEj@0^0?CHX@8l%CHkp1bYI zO~^hdogjZhviu4A1MLl5Q)YagKW|+`ZbClG1O5-wFLpJ(cs&!Cju|{%{an^LB{Ts5 Dcsm*= literal 0 HcmV?d00001 From 3227fe2ebd367560b06e9542b6f295a18c83ad02 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 14:02:37 +0200 Subject: [PATCH 029/424] Fixed and enabled logging for some functions Added missing include uncomment logging in find- and get_control as well as find- and get_button functions --- panda/src/device/inputDevice.I | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index ab30e9eb49..b09a89c7b1 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -11,6 +11,8 @@ * @date 2015-12-11 */ +#include "config_device.h" + /** * */ @@ -249,8 +251,8 @@ get_button(size_t index) const { if (index >= 0 && index < (int)_buttons.size()) { return _buttons[index]; } else { - /*device_cat.error() - << "Index " << index<< " was not found in the controls list\n";*/ + device_cat.error() + << "Index " << index<< " was not found in the controls list\n"; nassertr(false, ButtonState()); } } @@ -266,8 +268,8 @@ find_button(ButtonHandle handle) const { return _buttons[i]; } } - /*device_cat.error() - << "Axis " << axis << " was not found in the controls list\n";*/ + device_cat.error() + << "Handle " << handle.get_name() << " was not found in the controls list\n"; nassertr(false, ButtonState()); } @@ -335,8 +337,8 @@ get_control(size_t index) const { if (index >= 0 && index < (int)_controls.size()) { return _controls[index]; } else { - /*device_cat.error() - << "Index " << index<< " was not found in the controls list\n";*/ + device_cat.error() + << "Index " << index<< " was not found in the controls list\n"; nassertr(false, AnalogState()); } } @@ -352,8 +354,8 @@ find_control(ControlAxis axis) const { return _controls[i]; } } - /*device_cat.error() - << "Axis " << axis << " was not found in the controls list\n";*/ + device_cat.error() + << "Axis " << axis << " was not found in the controls list\n"; nassertr(false, AnalogState()); } From f1444fcff966069f82d86a4d7edfdefaf61f2c1a Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 16:44:09 +0200 Subject: [PATCH 030/424] Fixed << operator to be usable in .I file --- panda/src/device/inputDevice.I | 4 ++-- panda/src/device/inputDevice.h | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index b09a89c7b1..a6094c8c69 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -290,7 +290,7 @@ get_num_controls() const { * the various controls by index number. */ INLINE void InputDevice:: -set_control_map(int index, ControlAxis axis) { +set_control_map(int index, InputDevice::ControlAxis axis) { LightMutexHolder holder(_lock); nassertv(index >= 0); if (index >= (int)_controls.size()) { @@ -348,7 +348,7 @@ get_control(size_t index) const { * if the axis was not found in the list. */ INLINE InputDevice::AnalogState InputDevice:: -find_control(ControlAxis axis) const { +find_control(InputDevice::ControlAxis axis) const { for (int i; i < (int)_controls.size(); i++) { if (_controls[i].axis == axis) { return _controls[i]; diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index f036394b92..a007fbb081 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -287,7 +287,8 @@ PUBLISHED: INLINE AnalogState get_control(size_t index) const; INLINE AnalogState find_control(ControlAxis axis) const; - // Make device controls iterable + // Make device buttons and controls iterable + MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button); MAKE_SEQ_PROPERTY(controls, get_num_controls, get_control); public: @@ -313,8 +314,8 @@ INLINE ostream &operator << (ostream &out, const InputDevice &device) { return out; } -ostream &operator << (ostream &out, InputDevice::DeviceClass dc); -ostream &operator << (ostream &out, InputDevice::ControlAxis axis); +EXPCL_PANDA_DEVICE ostream &operator << (ostream &out, InputDevice::DeviceClass dc); +EXPCL_PANDA_DEVICE ostream &operator << (ostream &out, InputDevice::ControlAxis axis); #include "inputDevice.I" From 719e77a191f49c6accb777cd0b0a3465355762ff Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 16:46:20 +0200 Subject: [PATCH 031/424] Changed button mapping changed mapping of previous and next (i.e. wiimote's + and - keys) to the same as the select and start button --- panda/src/device/evdevInputDevice.cxx | 8 ++------ panda/src/device/linuxJoystickDevice.cxx | 10 ++-------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 573042c5d2..3edde84935 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -768,20 +768,16 @@ map_button(int code) { return GamepadButton::action_2(); case BTN_SELECT: + case KEY_PREVIOUS: return GamepadButton::back(); case BTN_START: + case KEY_NEXT: return GamepadButton::start(); case BTN_MODE: return GamepadButton::guide(); - case KEY_NEXT: - return GamepadButton::next(); - - case KEY_PREVIOUS: - return GamepadButton::previous(); - case BTN_THUMBL: return GamepadButton::lstick(); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index a1abddf3dd..fc2e2ba7b1 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -172,10 +172,12 @@ open_device() { break; case BTN_SELECT: + case KEY_PREVIOUS: handle = GamepadButton::back(); break; case BTN_START: + case KEY_NEXT: handle = GamepadButton::start(); break; @@ -183,14 +185,6 @@ open_device() { handle = GamepadButton::guide(); break; - case KEY_NEXT: - handle = GamepadButton::next(); - break; - - case KEY_PREVIOUS: - handle = GamepadButton::previous(); - break; - case BTN_THUMBL: handle = GamepadButton::lstick(); break; From f2a3a451005710a6722d5bffda38289086ccf279 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 16:46:39 +0200 Subject: [PATCH 032/424] added assert to debugging output --- direct/src/showbase/ShowBase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index b1e84d1aae..1aeddf4f66 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -1724,7 +1724,7 @@ class ShowBase(DirectObject.DirectObject): id+=1 # Setup the button thrower for that device and register it's event prefix bt = idn.attachNewNode(ButtonThrower(prefix)) - self.notify.debug("Registred event prefix {}{}-".format(prefix, id)) + assert self.notify.debug("Registered event prefix {}{}-".format(prefix, id)) bt.node().setPrefix("{}{}-".format(prefix, id)) # append the new button thrower to the list of device button throwers self.deviceButtonThrowers.append(bt) From dc1ec6b87747e221a19ab4ae42090ba84e51616d Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 10 Aug 2016 16:48:53 +0200 Subject: [PATCH 033/424] small cleanup of sample files Further documentation simplyfy lines by separately storing the value of findControl --- samples/gamepad/gamepad.py | 17 ++++++++++++----- samples/gamepad/steeringWheel.py | 8 +++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/samples/gamepad/gamepad.py b/samples/gamepad/gamepad.py index fe6cbd296f..82154838f6 100644 --- a/samples/gamepad/gamepad.py +++ b/samples/gamepad/gamepad.py @@ -56,16 +56,19 @@ class App(ShowBase): # movements by ourself self.disableMouse() + # list of connected gamepad devices + gamepads = base.devices.getDevices(InputDevice.DC_gamepad) + # set the center position of the control sticks # NOTE: here we assume, that the wheel is centered when the application get started. # In real world applications, you should notice the user and give him enough time # to center the wheel until you store the center position of the controler! - gamepads = base.devices.getDevices(InputDevice.DC_gamepad) self.lxcenter = gamepads[0].findControl(InputDevice.C_left_x).state self.lycenter = gamepads[0].findControl(InputDevice.C_left_y).state self.rxcenter = gamepads[0].findControl(InputDevice.C_right_x).state self.rycenter = gamepads[0].findControl(InputDevice.C_right_y).state + self.taskMgr.add(self.moveTask, "movement update task") def connect(self, device): @@ -106,13 +109,17 @@ class App(ShowBase): # we will use the first found gamepad # Move the camera left/right - movementVec.setX(gamepads[0].findControl(InputDevice.C_left_x).state - self.lxcenter) + left_x = gamepads[0].findControl(InputDevice.C_left_x) + movementVec.setX(left_x.state - self.lxcenter) # Move the camera forward/backward - movementVec.setY(gamepads[0].findControl(InputDevice.C_left_y).state - self.lycenter) + left_y = gamepads[0].findControl(InputDevice.C_left_y) + movementVec.setY(left_y.state - self.lycenter) # Control the cameras heading - base.camera.setH(base.camera, 100 * dt * (gamepads[0].findControl(InputDevice.C_right_x).state - self.rxcenter)) + right_x = gamepads[0].findControl(InputDevice.C_right_x) + base.camera.setH(base.camera, 100 * dt * (right_x.state - self.rxcenter)) # Control the cameras pitch - base.camera.setP(base.camera, 100 * dt * (gamepads[0].findControl(InputDevice.C_right_y).state - self.rycenter)) + right_y = gamepads[0].findControl(InputDevice.C_right_y) + base.camera.setP(base.camera, 100 * dt * (right_y.state - self.rycenter)) # calculate movement base.camera.setX(base.camera, 100 * dt * movementVec.getX()) diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py index 256c2e1eb3..306419c378 100644 --- a/samples/gamepad/steeringWheel.py +++ b/samples/gamepad/steeringWheel.py @@ -114,13 +114,15 @@ class App(ShowBase): # we will use the first found wheel # Acclerate - accleration = wheels[0].findControl(InputDevice.C_accelerator).state * self.maxAccleration - if self.currentMoveSpeed > wheels[0].findControl(InputDevice.C_accelerator).state * self.maxSpeed: + acclearatorPedal = wheels[0].findControl(InputDevice.C_accelerator).state + accleration = accleratorPedal * self.maxAccleration + if self.currentMoveSpeed > accleratorPedal * self.maxSpeed: self.currentMoveSpeed -= dt * self.deaccleration self.currentMoveSpeed += dt * accleration # Break - deacleration = wheels[0].findControl(InputDevice.C_brake).state * self.deaclerationBreak + breakPedal = wheels[0].findControl(InputDevice.C_brake).state + deacleration = breakPedal * self.deaclerationBreak self.currentMoveSpeed -= dt * deacleration if self.currentMoveSpeed < 0: self.currentMoveSpeed = 0 From 2da890800fe01eb0bd019eeed76c986b739a835b Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Thu, 11 Aug 2016 12:05:07 +0200 Subject: [PATCH 034/424] Fix hat mapping if dpad emulation is deactivated --- panda/src/device/evdevInputDevice.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 3edde84935..39ba47b236 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -386,6 +386,10 @@ init_device() { _dpad_up_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } else if (i == ABS_HAT0X) { + set_control_map(i, C_hat_x); + } else if (i == ABS_HAT0Y) { + set_control_map(i, C_hat_y); } } else { set_control_map(i, axis_map[i]); From aa6e7229410de745169ed127da551eaec43c9a95 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 2 Nov 2016 11:27:43 -0700 Subject: [PATCH 035/424] Create experimental yapdt (Yet Another Panda3D Deployment Tool) This uses FreezeTool and a new deploy-stub binary to create frozen applications without needing a compiler when running the tool. This tool is for experimenting with deployment options and is currently inflexible and hacky. --- direct/src/showutil/yapdt.py | 96 +++++++++++ makepanda/makepanda.py | 9 ++ pandatool/src/deploy-stub/deploy-stub.c | 207 ++++++++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100755 direct/src/showutil/yapdt.py create mode 100644 pandatool/src/deploy-stub/deploy-stub.c diff --git a/direct/src/showutil/yapdt.py b/direct/src/showutil/yapdt.py new file mode 100755 index 0000000000..3eb9829807 --- /dev/null +++ b/direct/src/showutil/yapdt.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +import marshal +import os +import struct + +from direct.showutil import FreezeTool +import panda3d.core as p3d + +def make_module_list_entry(code, offset, modulename, module): + size = len(code) + if getattr(module, "__path__", None): + # Indicate package by negative size + size = -size + return struct.pack('<256sIi', bytes(modulename, 'ascii'), offset, size) + +def make_forbidden_module_list_entry(modulename): + return struct.pack('<256sIi', bytes(modulename, 'ascii'), 0, 0) + +def get_modules(freezer): + # Now generate the actual export table. + moduleBlob = bytes() + codeOffset = 0 + moduleList = [] + + for moduleName, mdef in freezer.getModuleDefs(): + origName = mdef.moduleName + if mdef.forbid: + # Explicitly disallow importing this module. + moduleList.append(make_forbidden_module_list_entry(moduleName)) + continue + + assert not mdef.exclude + # Allow importing this module. + module = freezer.mf.modules.get(origName, None) + code = getattr(module, "__code__", None) + if code: + code = marshal.dumps(code) + moduleList.append(make_module_list_entry(code, codeOffset, moduleName, module)) + moduleBlob += code + codeOffset += len(code) + continue + + # This is a module with no associated Python code. It is either + # an extension module or a builtin module. Get the filename, if + # it is the former. + extensionFilename = getattr(module, '__file__', None) + + if extensionFilename or freezer.linkExtensionModules: + freezer.extras.append((moduleName, extensionFilename)) + + # If it is a submodule of a frozen module, Python will have + # trouble importing it as a builtin module. Synthesize a frozen + # module that loads it as builtin. + if '.' in moduleName and freezer.linkExtensionModules: + code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec') + code = marshal.dumps(code) + moduleList.append(make_module_list_entry(code, codeOffset, moduleName, module)) + moduleBlob += code + codeOffset += len(code) + elif '.' in moduleName: + # Nothing we can do about this case except warn the user they + # are in for some trouble. + print('WARNING: Python cannot import extension modules under ' + 'frozen Python packages; %s will be inaccessible. ' + 'passing either -l to link in extension modules or use ' + '-x %s to exclude the entire package.' % (moduleName, moduleName.split('.')[0])) + + return moduleBlob, moduleList + + +if __name__ == '__main__': + # Setup Freezer + freezer = FreezeTool.Freezer() + freezer.keepTemporaryFiles = True + freezer.addModule('__main__', filename='app.py') + freezer.done(addStartupModules=True) + + # Build from pre-built binary stub + stub_path = os.path.join(os.path.dirname(p3d.ExecutionEnvironment.get_dtool_name()), '..', 'bin', 'deploy-stub') + out_path = 'frozen_app' + with open(stub_path, 'rb') as f: + stubbin = f.read() + + modblob, modlist = get_modules(freezer) + + with open(out_path, 'wb') as f: + f.write(stubbin) + listoffset = f.tell() + for mod in modlist: + f.write(mod) + modsoffset = f.tell() + f.write(modblob) + f.write(struct.pack(' + +#if PY_MAJOR_VERSION >= 3 +#include +#endif + +#ifdef MS_WINDOWS +extern void PyWinFreeze_ExeInit(void); +extern void PyWinFreeze_ExeTerm(void); + +extern DL_IMPORT(int) PyImport_ExtendInittab(struct _inittab *newtab); +#endif + +static unsigned char *modblob = NULL; + +/* Main program */ + +int +Py_FrozenMain(int argc, char **argv) +{ + char *p; + int n, sts = 1; + int inspect = 0; + int unbuffered = 0; + +#if PY_MAJOR_VERSION >= 3 + int i; + char *oldloc = NULL; + wchar_t **argv_copy = NULL; + /* We need a second copies, as Python might modify the first one. */ + wchar_t **argv_copy2 = NULL; + + if (argc > 0) { + argv_copy = PyMem_RawMalloc(sizeof(wchar_t*) * argc); + argv_copy2 = PyMem_RawMalloc(sizeof(wchar_t*) * argc); + if (!argv_copy || !argv_copy2) { + fprintf(stderr, "out of memory\n"); + goto error; + } + } +#endif + + Py_FrozenFlag = 1; /* Suppress errors from getpath.c */ + Py_NoSiteFlag = 1; + Py_NoUserSiteDirectory = 1; + + if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0') + inspect = 1; + if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0') + unbuffered = 1; + + if (unbuffered) { + setbuf(stdin, (char *)NULL); + setbuf(stdout, (char *)NULL); + setbuf(stderr, (char *)NULL); + } + +#if PY_MAJOR_VERSION >= 3 + oldloc = _PyMem_RawStrdup(setlocale(LC_ALL, NULL)); + if (!oldloc) { + fprintf(stderr, "out of memory\n"); + goto error; + } + + setlocale(LC_ALL, ""); + for (i = 0; i < argc; i++) { + argv_copy[i] = Py_DecodeLocale(argv[i], NULL); + argv_copy2[i] = argv_copy[i]; + if (!argv_copy[i]) { + fprintf(stderr, "Unable to decode the command line argument #%i\n", + i + 1); + argc = i; + goto error; + } + } + setlocale(LC_ALL, oldloc); + PyMem_RawFree(oldloc); + oldloc = NULL; +#endif + +#ifdef MS_WINDOWS + PyImport_ExtendInittab(extensions); +#endif /* MS_WINDOWS */ + + if (argc >= 1) { +#if PY_MAJOR_VERSION >= 3 + Py_SetProgramName(argv_copy[0]); +#else + Py_SetProgramName(argv[0]); +#endif + } + + Py_Initialize(); +#ifdef MS_WINDOWS + PyWinFreeze_ExeInit(); +#endif + + if (Py_VerboseFlag) + fprintf(stderr, "Python %s\n%s\n", + Py_GetVersion(), Py_GetCopyright()); + +#if PY_MAJOR_VERSION >= 3 + PySys_SetArgv(argc, argv_copy); +#else + PySys_SetArgv(argc, argv); +#endif + + n = PyImport_ImportFrozenModule("__main__"); + if (n == 0) + Py_FatalError("__main__ not frozen"); + if (n < 0) { + PyErr_Print(); + sts = 1; + } + else + sts = 0; + + n = PyImport_ImportFrozenModule("_frozen_importlib"); + if (n == 0) + Py_FatalError("_frozen_importlib not frozen"); + if (n < 0) + PyErr_Print(); + + if (inspect && isatty((int)fileno(stdin))) + sts = PyRun_AnyFile(stdin, "") != 0; + +#ifdef MS_WINDOWS + PyWinFreeze_ExeTerm(); +#endif + Py_Finalize(); + +#if PY_MAJOR_VERSION >= 3 +error: + PyMem_RawFree(argv_copy); + if (argv_copy2) { + for (i = 0; i < argc; i++) + PyMem_RawFree(argv_copy2[i]); + PyMem_RawFree(argv_copy2); + } + PyMem_RawFree(oldloc); +#endif + return sts; +} + + +int +main(int argc, char *argv[]) { + struct _frozen *_PyImport_FrozenModules; + unsigned int listoff, modsoff, fsize, modsize, listsize, nummods; + FILE *runtime = fopen(argv[0], "rb"); + + // Get offsets + fseek(runtime, -12, SEEK_END); + fsize = ftell(runtime); + fread(&listoff, 4, 1, runtime); + fread(&modsoff, 4, 1, runtime); + fread(&nummods, 4, 1, runtime); + modsize = fsize - modsoff; + listsize = modsoff - listoff; + + // Read module blob + modblob = malloc(modsize); + fseek(runtime, modsoff, SEEK_SET); + fread(modblob, modsize, 1, runtime); + + // Read module list + _PyImport_FrozenModules = calloc(nummods + 1, sizeof(struct _frozen)); + fseek(runtime, listoff, SEEK_SET); + for (unsigned int modidx = 0; modidx < nummods; ++modidx) { + struct _frozen *moddef = &_PyImport_FrozenModules[modidx]; + char *name = NULL, namebuf[256] = {0}; + unsigned int nsize, codeptr; + int codesize; + + // Name + fread(namebuf, 1, 256, runtime); + nsize = strlen(namebuf) + 1; + name = malloc(nsize); + memcpy(name, namebuf, nsize); + moddef->name = name; + + // Pointer + fread(&codeptr, 4, 1, runtime); + moddef->code = modblob + codeptr; + + // Size + fread(&codesize, 4, 1, runtime); + moddef->size = codesize; + } + + // Uncomment this to print out the read in module list + //for (unsigned int modidx = 0; modidx < nummods + 1; ++modidx) { + // struct _frozen *moddef = &_PyImport_FrozenModules[modidx]; + // printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size); + //} + fclose(runtime); + + PyImport_FrozenModules = _PyImport_FrozenModules; + return Py_FrozenMain(argc, argv); +} From b1a57643f994ac83dd376aa1ff8c429f82fd8de0 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 2 Nov 2016 14:04:48 -0700 Subject: [PATCH 036/424] Move runtime generation logic from yapdt to FreezeTool --- direct/src/showutil/FreezeTool.py | 88 ++++++++++++++++++++++++++++++ direct/src/showutil/yapdt.py | 91 ++----------------------------- 2 files changed, 92 insertions(+), 87 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index c734c56101..b0c1af3d00 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -7,6 +7,7 @@ import os import marshal import imp import platform +import struct from io import StringIO import distutils.sysconfig as sysconf @@ -1549,6 +1550,93 @@ class Freezer: return target + def generateRuntimeFromStub(self, basename): + def make_module_list_entry(code, offset, modulename, module): + size = len(code) + if getattr(module, "__path__", None): + # Indicate package by negative size + size = -size + return struct.pack('<256sIi', bytes(modulename, 'ascii'), offset, size) + + def make_forbidden_module_list_entry(modulename): + return struct.pack('<256sIi', bytes(modulename, 'ascii'), 0, 0) + + # We must have a __main__ module to make an exe file. + if not self.__writingModule('__main__'): + message = "Can't generate an executable without a __main__ module." + raise Exception(message) + + if self.platform.startswith('win'): + target = basename + '.exe' + else: + target = basename + + + # Generate export table. + moduleBlob = bytes() + codeOffset = 0 + moduleList = [] + + for moduleName, mdef in self.getModuleDefs(): + origName = mdef.moduleName + if mdef.forbid: + # Explicitly disallow importing this module. + moduleList.append(make_forbidden_module_list_entry(moduleName)) + continue + + assert not mdef.exclude + # Allow importing this module. + module = self.mf.modules.get(origName, None) + code = getattr(module, "__code__", None) + if code: + code = marshal.dumps(code) + moduleList.append(make_module_list_entry(code, codeOffset, moduleName, module)) + moduleBlob += code + codeOffset += len(code) + continue + + # This is a module with no associated Python code. It is either + # an extension module or a builtin module. Get the filename, if + # it is the former. + extensionFilename = getattr(module, '__file__', None) + + if extensionFilename or self.linkExtensionModules: + self.extras.append((moduleName, extensionFilename)) + + # If it is a submodule of a frozen module, Python will have + # trouble importing it as a builtin module. Synthesize a frozen + # module that loads it as builtin. + if '.' in moduleName and self.linkExtensionModules: + code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec') + code = marshal.dumps(code) + moduleList.append(make_module_list_entry(code, codeOffset, moduleName, module)) + moduleBlob += code + codeOffset += len(code) + elif '.' in moduleName: + # Nothing we can do about this case except warn the user they + # are in for some trouble. + print('WARNING: Python cannot import extension modules under ' + 'frozen Python packages; %s will be inaccessible. ' + 'passing either -l to link in extension modules or use ' + '-x %s to exclude the entire package.' % (moduleName, moduleName.split('.')[0])) + + # Build from pre-built binary stub + stub_path = os.path.join(os.path.dirname(ExecutionEnvironment.get_dtool_name()), '..', 'bin', 'deploy-stub') + with open(stub_path, 'rb') as f: + stubbin = f.read() + + with open(target, 'wb') as f: + f.write(stubbin) + listoffset = f.tell() + for mod in moduleList: + f.write(mod) + modsoffset = f.tell() + f.write(moduleBlob) + f.write(struct.pack(' Date: Wed, 2 Nov 2016 14:54:49 -0700 Subject: [PATCH 037/424] Adding a dist module that can be imported to use FreezeTool as part of setup.py --- direct/src/showutil/dist.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 direct/src/showutil/dist.py diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py new file mode 100644 index 0000000000..e84cc3d3d9 --- /dev/null +++ b/direct/src/showutil/dist.py @@ -0,0 +1,34 @@ +import os + +import distutils.dist +import distutils.command.build +import distutils.core + +from direct.showutil import FreezeTool + +class Distribution(distutils.dist.Distribution): + def __init__(self, attrs): + self.mainfile = '' + distutils.dist.Distribution.__init__(self, attrs) + + +class build(distutils.command.build.build): + def run(self): + distutils.command.build.build.run(self) + basename = os.path.join(self.build_base, self.distribution.get_fullname()) + startfile = self.distribution.mainfile + + if not os.path.exists(self.build_base): + os.makedirs(self.build_base) + + freezer = FreezeTool.Freezer() + freezer.addModule('__main__', filename=startfile) + freezer.done(addStartupModules=True) + freezer.generateRuntimeFromStub(basename) + + +def setup(**attrs): + attrs.setdefault("distclass", Distribution) + commandClasses = attrs.setdefault("cmdclass", {}) + commandClasses['build'] = build + distutils.core.setup(**attrs) From e9ae6b5bb12fce773ea8d38eb655f39d7894e267 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 2 Nov 2016 15:17:26 -0700 Subject: [PATCH 038/424] Copy game files when using setup.py --- direct/src/showutil/dist.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index e84cc3d3d9..fbdfb0a27a 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1,4 +1,6 @@ import os +import shutil +import sys import distutils.dist import distutils.command.build @@ -8,14 +10,15 @@ from direct.showutil import FreezeTool class Distribution(distutils.dist.Distribution): def __init__(self, attrs): - self.mainfile = '' + self.mainfile = 'main.py' + self.game_dir = 'game' distutils.dist.Distribution.__init__(self, attrs) class build(distutils.command.build.build): def run(self): distutils.command.build.build.run(self) - basename = os.path.join(self.build_base, self.distribution.get_fullname()) + basename = os.path.abspath(os.path.join(self.build_base, self.distribution.get_fullname())) startfile = self.distribution.mainfile if not os.path.exists(self.build_base): @@ -23,9 +26,24 @@ class build(distutils.command.build.build): freezer = FreezeTool.Freezer() freezer.addModule('__main__', filename=startfile) + freezer.excludeModule('panda3d') freezer.done(addStartupModules=True) freezer.generateRuntimeFromStub(basename) + gamedir = self.distribution.game_dir + for item in os.listdir(gamedir): + if item in ('__pycache__', startfile): + continue + src = os.path.join(gamedir, item) + dst = os.path.join(self.build_base, item) + + if os.path.isdir(src): + print("Copy dir", src, dst) + shutil.copytree(src, dst) + else: + print("Copy file", src, dst) + shutil.copy(src, dst) + def setup(**attrs): attrs.setdefault("distclass", Distribution) From df7e3f03e264007c481735add500dfde9cb89553 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 2 Nov 2016 15:20:36 -0700 Subject: [PATCH 039/424] Fix mainfile option in setup.py --- direct/src/showutil/dist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index fbdfb0a27a..a6a52b42ee 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -19,7 +19,8 @@ class build(distutils.command.build.build): def run(self): distutils.command.build.build.run(self) basename = os.path.abspath(os.path.join(self.build_base, self.distribution.get_fullname())) - startfile = self.distribution.mainfile + gamedir = self.distribution.game_dir + startfile = os.path.join(gamedir, self.distribution.mainfile) if not os.path.exists(self.build_base): os.makedirs(self.build_base) @@ -30,7 +31,6 @@ class build(distutils.command.build.build): freezer.done(addStartupModules=True) freezer.generateRuntimeFromStub(basename) - gamedir = self.distribution.game_dir for item in os.listdir(gamedir): if item in ('__pycache__', startfile): continue From a628dd6edc0331ecfbac8e6cb9497e6c94aeb284 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 9 Nov 2016 17:50:54 -0800 Subject: [PATCH 040/424] Distutils: Skip copying frozen Python packages --- direct/src/showutil/dist.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index a6a52b42ee..d490397e28 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -18,12 +18,13 @@ class Distribution(distutils.dist.Distribution): class build(distutils.command.build.build): def run(self): distutils.command.build.build.run(self) - basename = os.path.abspath(os.path.join(self.build_base, self.distribution.get_fullname())) + builddir = self.build_base + basename = os.path.abspath(os.path.join(builddir, self.distribution.get_fullname())) gamedir = self.distribution.game_dir startfile = os.path.join(gamedir, self.distribution.mainfile) - if not os.path.exists(self.build_base): - os.makedirs(self.build_base) + if not os.path.exists(builddir): + os.makedirs(builddir) freezer = FreezeTool.Freezer() freezer.addModule('__main__', filename=startfile) @@ -31,11 +32,19 @@ class build(distutils.command.build.build): freezer.done(addStartupModules=True) freezer.generateRuntimeFromStub(basename) + ignore_copy_list = [ + '__pycache__', + self.distribution.mainfile, + *freezer.getAllModuleNames(), + ] + for item in os.listdir(gamedir): - if item in ('__pycache__', startfile): - continue src = os.path.join(gamedir, item) - dst = os.path.join(self.build_base, item) + dst = os.path.join(builddir, item) + + if item in ignore_copy_list: + print("Skip", src) + continue if os.path.isdir(src): print("Copy dir", src, dst) From 9921cd5a973fbb7651b8b69fc104d43ffbb33258 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 9 Nov 2016 18:00:57 -0800 Subject: [PATCH 041/424] Distutils: Create per-platform build directories --- direct/src/showutil/dist.py | 67 ++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index d490397e28..34d956b47f 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1,12 +1,14 @@ import os -import shutil import sys -import distutils.dist import distutils.command.build import distutils.core +import distutils.dir_util +import distutils.dist +import distutils.file_util from direct.showutil import FreezeTool +import panda3d.core as p3d class Distribution(distutils.dist.Distribution): def __init__(self, attrs): @@ -18,40 +20,45 @@ class Distribution(distutils.dist.Distribution): class build(distutils.command.build.build): def run(self): distutils.command.build.build.run(self) - builddir = self.build_base - basename = os.path.abspath(os.path.join(builddir, self.distribution.get_fullname())) - gamedir = self.distribution.game_dir - startfile = os.path.join(gamedir, self.distribution.mainfile) + platforms = [p3d.PandaSystem.get_platform()] - if not os.path.exists(builddir): - os.makedirs(builddir) + for platform in platforms: + builddir = os.path.join(self.build_base, platform) - freezer = FreezeTool.Freezer() - freezer.addModule('__main__', filename=startfile) - freezer.excludeModule('panda3d') - freezer.done(addStartupModules=True) - freezer.generateRuntimeFromStub(basename) + if os.path.exists(builddir): + distutils.dir_util.remove_tree(builddir) + distutils.dir_util.mkpath(builddir) - ignore_copy_list = [ - '__pycache__', - self.distribution.mainfile, - *freezer.getAllModuleNames(), - ] + basename = os.path.abspath(os.path.join(builddir, self.distribution.get_fullname())) + gamedir = self.distribution.game_dir + startfile = os.path.join(gamedir, self.distribution.mainfile) - for item in os.listdir(gamedir): - src = os.path.join(gamedir, item) - dst = os.path.join(builddir, item) + freezer = FreezeTool.Freezer() + freezer.addModule('__main__', filename=startfile) + freezer.excludeModule('panda3d') + freezer.done(addStartupModules=True) + freezer.generateRuntimeFromStub(basename) - if item in ignore_copy_list: - print("Skip", src) - continue + ignore_copy_list = [ + '__pycache__', + self.distribution.mainfile, + *freezer.getAllModuleNames(), + ] - if os.path.isdir(src): - print("Copy dir", src, dst) - shutil.copytree(src, dst) - else: - print("Copy file", src, dst) - shutil.copy(src, dst) + for item in os.listdir(gamedir): + src = os.path.join(gamedir, item) + dst = os.path.join(builddir, item) + + if item in ignore_copy_list: + print("skipping", src) + continue + + if os.path.isdir(src): + #print("Copy dir", src, dst) + distutils.dir_util.copy_tree(src, dst) + else: + #print("Copy file", src, dst) + distutils.file_util.copy_file(src, dst) def setup(**attrs): From 8d44e73fcc7f6498dde6d8f637cf4161224beddc Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 9 Nov 2016 18:10:49 -0800 Subject: [PATCH 042/424] Remove yapdt Distutils/setup.py should be used instead. --- direct/src/showutil/yapdt.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100755 direct/src/showutil/yapdt.py diff --git a/direct/src/showutil/yapdt.py b/direct/src/showutil/yapdt.py deleted file mode 100755 index 9b0dced6a4..0000000000 --- a/direct/src/showutil/yapdt.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python -from direct.showutil import FreezeTool - -if __name__ == '__main__': - start_file = 'app.py' - basename = 'built_app_ng' - # Setup Freezer - freezer = FreezeTool.Freezer() - freezer.keepTemporaryFiles = True - freezer.addModule('__main__', filename=start_file) - freezer.done(addStartupModules=True) - - freezer.generateRuntimeFromStub(basename) From c20826534381599957503a0531cc4f39483e87f5 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 13 Nov 2016 10:03:37 -0800 Subject: [PATCH 043/424] Distutils: Add exclude_modules option --- direct/src/showutil/dist.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 34d956b47f..fc06f851cc 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -14,6 +14,7 @@ class Distribution(distutils.dist.Distribution): def __init__(self, attrs): self.mainfile = 'main.py' self.game_dir = 'game' + self.exclude_modules = [] distutils.dist.Distribution.__init__(self, attrs) @@ -36,6 +37,8 @@ class build(distutils.command.build.build): freezer = FreezeTool.Freezer() freezer.addModule('__main__', filename=startfile) freezer.excludeModule('panda3d') + for exmod in self.distribution.exclude_modules: + freezer.excludeModule(exmod) freezer.done(addStartupModules=True) freezer.generateRuntimeFromStub(basename) From b11ec02acba7687cb3a46d8db8ecbd4dbb61370f Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 13 Nov 2016 10:09:42 -0800 Subject: [PATCH 044/424] Distutils: Copy over panda3d and libs TODO: Currently all of the libs and the whole panda3d module gets copied over. We should make use of Packager code to trim this down. --- direct/src/showutil/dist.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index fc06f851cc..121f517217 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -34,6 +34,7 @@ class build(distutils.command.build.build): gamedir = self.distribution.game_dir startfile = os.path.join(gamedir, self.distribution.mainfile) + # Create runtime freezer = FreezeTool.Freezer() freezer.addModule('__main__', filename=startfile) freezer.excludeModule('panda3d') @@ -42,6 +43,23 @@ class build(distutils.command.build.build): freezer.done(addStartupModules=True) freezer.generateRuntimeFromStub(basename) + # Copy Panda3D libs + dtool_dir = os.path.dirname(p3d.ExecutionEnvironment.get_dtool_name()) + libdir = os.path.join(dtool_dir, '..', 'lib') + src = os.path.join(libdir, '..', 'panda3d') + dst = os.path.join(builddir, 'panda3d') + distutils.dir_util.copy_tree(src, dst) + + for item in os.listdir(libdir): + if '.so.' in item or item.endswith('.dll') or 'libpandagl' in item: + distutils.file_util.copy_file(os.path.join(libdir, item), os.path.join(builddir, item)) + + # Copy etc + src = os.path.join(dtool_dir, '..', 'etc') + dst = os.path.join(builddir, 'etc') + distutils.dir_util.copy_tree(src, dst) + + # Copy Game Files ignore_copy_list = [ '__pycache__', self.distribution.mainfile, From f61fbd356dff9ab0bef90861a9233e69205b2fce Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 13 Nov 2016 11:55:43 -0800 Subject: [PATCH 045/424] Set rpath on deploy-stub to $ORIGIN for Linux builds This allows game runtimes to search their directory for shared libraries instead of needing to use LD_LIBRARY_PATH. --- makepanda/makepanda.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 92a93d21fd..2302e177a2 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6303,8 +6303,12 @@ if (PkgSkip("CONTRIB")==0 and not RUNTIME): if True: # TODO OPTS=['DIR:pandatool/src/deploy-stub', 'BUILDING:DEPLOYSTUB', 'PYTHON'] TargetAdd('deploy-stub.obj', opts=OPTS, input='deploy-stub.c') + if GetTarget() == 'linux': + # Setup rpath so libs can be found in the same directory as the deployed game + LibName('DEPLOYSTUB', "-Wl,-rpath,\$ORIGIN") + LibName('DEPLOYSTUB', "-Wl,-z,origin") TargetAdd('deploy-stub.exe', input='deploy-stub.obj') - TargetAdd('deploy-stub.exe', opts=['PYTHON']) + TargetAdd('deploy-stub.exe', opts=['PYTHON', 'DEPLOYSTUB']) # # Generate the models directory and samples directory From b9b8782320a80e911d6721572529f15137f4b4fc Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 13 Nov 2016 11:57:33 -0800 Subject: [PATCH 046/424] Distutils: Add extras build option to copy extra files not normally copied (e.g., ones outside of the game directory) --- direct/src/showutil/dist.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 121f517217..41b1ca5460 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -15,6 +15,7 @@ class Distribution(distutils.dist.Distribution): self.mainfile = 'main.py' self.game_dir = 'game' self.exclude_modules = [] + self.extras = [] distutils.dist.Distribution.__init__(self, attrs) @@ -81,6 +82,16 @@ class build(distutils.command.build.build): #print("Copy file", src, dst) distutils.file_util.copy_file(src, dst) + # Copy extra files + for extra in self.distribution.extras: + if len(extra) == 2: + src, dst = extra + dst = os.path.join(builddir, dst) + else: + src = extra + dst = builddir + distutils.file_util.copy_file(src, dst) + def setup(**attrs): attrs.setdefault("distclass", Distribution) From f001bd20d73e353dbf63f48511c05c5af886e4b6 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 5 Dec 2016 18:54:37 -0500 Subject: [PATCH 047/424] deploy-ng: fix issues with Mac/Python 2 build --- direct/src/showutil/FreezeTool.py | 4 ++-- direct/src/showutil/dist.py | 10 ++++++---- pandatool/src/deploy-stub/deploy-stub.c | 6 ++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index b0c1af3d00..c32899cee2 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1556,10 +1556,10 @@ class Freezer: if getattr(module, "__path__", None): # Indicate package by negative size size = -size - return struct.pack('<256sIi', bytes(modulename, 'ascii'), offset, size) + return struct.pack('<256sIi', modulename.encode('ascii'), offset, size) def make_forbidden_module_list_entry(modulename): - return struct.pack('<256sIi', bytes(modulename, 'ascii'), 0, 0) + return struct.pack('<256sIi', modulename.encode('ascii'), 0, 0) # We must have a __main__ module to make an exe file. if not self.__writingModule('__main__'): diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 41b1ca5460..dda2e9f3e7 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -52,8 +52,11 @@ class build(distutils.command.build.build): distutils.dir_util.copy_tree(src, dst) for item in os.listdir(libdir): - if '.so.' in item or item.endswith('.dll') or 'libpandagl' in item: - distutils.file_util.copy_file(os.path.join(libdir, item), os.path.join(builddir, item)) + if '.so.' in item or item.endswith('.dll') or item.endswith('.dylib') or 'libpandagl' in item: + source_path = os.path.join(libdir, item) + target_path = os.path.join(builddir, item) + if not os.path.islink(source_path): + distutils.file_util.copy_file(source_path, target_path) # Copy etc src = os.path.join(dtool_dir, '..', 'etc') @@ -64,8 +67,7 @@ class build(distutils.command.build.build): ignore_copy_list = [ '__pycache__', self.distribution.mainfile, - *freezer.getAllModuleNames(), - ] + ] + freezer.getAllModuleNames() for item in os.listdir(gamedir): src = os.path.join(gamedir, item) diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 12ae6a3b63..ec85eacade 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -122,11 +122,13 @@ Py_FrozenMain(int argc, char **argv) else sts = 0; +#if PY_MAJOR_VERSION >= 3 n = PyImport_ImportFrozenModule("_frozen_importlib"); if (n == 0) Py_FatalError("_frozen_importlib not frozen"); if (n < 0) PyErr_Print(); +#endif if (inspect && isatty((int)fileno(stdin))) sts = PyRun_AnyFile(stdin, "") != 0; @@ -153,7 +155,7 @@ error: int main(int argc, char *argv[]) { struct _frozen *_PyImport_FrozenModules; - unsigned int listoff, modsoff, fsize, modsize, listsize, nummods; + unsigned int listoff, modsoff, fsize, modsize, listsize, nummods, modidx; FILE *runtime = fopen(argv[0], "rb"); // Get offsets @@ -173,7 +175,7 @@ main(int argc, char *argv[]) { // Read module list _PyImport_FrozenModules = calloc(nummods + 1, sizeof(struct _frozen)); fseek(runtime, listoff, SEEK_SET); - for (unsigned int modidx = 0; modidx < nummods; ++modidx) { + for (modidx = 0; modidx < nummods; ++modidx) { struct _frozen *moddef = &_PyImport_FrozenModules[modidx]; char *name = NULL, namebuf[256] = {0}; unsigned int nsize, codeptr; From 4335e3759ad9fa5f5fd07da07c6124bca7c43182 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 6 Dec 2016 02:14:39 +0100 Subject: [PATCH 048/424] Update Py_FrozenMain to work with Python 3.2 --- direct/src/showutil/FreezeTool.py | 33 ++++++++++------------ pandatool/src/deploy-stub/deploy-stub.c | 37 +++++++------------------ 2 files changed, 24 insertions(+), 46 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index c32899cee2..77f2be1a5a 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -251,6 +251,10 @@ frozenMainCode = """ #if PY_MAJOR_VERSION >= 3 #include + +#if PY_MINOR_VERSION < 5 +#define Py_DecodeLocale _Py_char2wchar +#endif #endif #ifdef MS_WINDOWS @@ -272,18 +276,14 @@ Py_FrozenMain(int argc, char **argv) #if PY_MAJOR_VERSION >= 3 int i; - char *oldloc = NULL; + char *oldloc; wchar_t **argv_copy = NULL; /* We need a second copies, as Python might modify the first one. */ wchar_t **argv_copy2 = NULL; if (argc > 0) { - argv_copy = PyMem_RawMalloc(sizeof(wchar_t*) * argc); - argv_copy2 = PyMem_RawMalloc(sizeof(wchar_t*) * argc); - if (!argv_copy || !argv_copy2) { - fprintf(stderr, \"out of memory\\n\"); - goto error; - } + argv_copy = (wchar_t **)alloca(sizeof(wchar_t *) * argc); + argv_copy2 = (wchar_t **)alloca(sizeof(wchar_t *) * argc); } #endif @@ -303,12 +303,7 @@ Py_FrozenMain(int argc, char **argv) } #if PY_MAJOR_VERSION >= 3 - oldloc = _PyMem_RawStrdup(setlocale(LC_ALL, NULL)); - if (!oldloc) { - fprintf(stderr, \"out of memory\\n\"); - goto error; - } - + oldloc = setlocale(LC_ALL, NULL); setlocale(LC_ALL, \"\"); for (i = 0; i < argc; i++) { argv_copy[i] = Py_DecodeLocale(argv[i], NULL); @@ -321,8 +316,6 @@ Py_FrozenMain(int argc, char **argv) } } setlocale(LC_ALL, oldloc); - PyMem_RawFree(oldloc); - oldloc = NULL; #endif #ifdef MS_WINDOWS @@ -372,13 +365,15 @@ Py_FrozenMain(int argc, char **argv) #if PY_MAJOR_VERSION >= 3 error: - PyMem_RawFree(argv_copy); if (argv_copy2) { - for (i = 0; i < argc; i++) + for (i = 0; i < argc; i++) { +#if PY_MINOR_VERSION >= 4 PyMem_RawFree(argv_copy2[i]); - PyMem_RawFree(argv_copy2); +#else + PyMem_Free(argv_copy2[i]); +#endif + } } - PyMem_RawFree(oldloc); #endif return sts; } diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index ec85eacade..14fbe6fbd3 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -32,18 +32,14 @@ Py_FrozenMain(int argc, char **argv) #if PY_MAJOR_VERSION >= 3 int i; - char *oldloc = NULL; + char *oldloc; wchar_t **argv_copy = NULL; /* We need a second copies, as Python might modify the first one. */ wchar_t **argv_copy2 = NULL; if (argc > 0) { - argv_copy = PyMem_RawMalloc(sizeof(wchar_t*) * argc); - argv_copy2 = PyMem_RawMalloc(sizeof(wchar_t*) * argc); - if (!argv_copy || !argv_copy2) { - fprintf(stderr, "out of memory\n"); - goto error; - } + argv_copy = (wchar_t **)alloca(sizeof(wchar_t *) * argc); + argv_copy2 = (wchar_t **)alloca(sizeof(wchar_t *) * argc); } #endif @@ -63,12 +59,7 @@ Py_FrozenMain(int argc, char **argv) } #if PY_MAJOR_VERSION >= 3 - oldloc = _PyMem_RawStrdup(setlocale(LC_ALL, NULL)); - if (!oldloc) { - fprintf(stderr, "out of memory\n"); - goto error; - } - + oldloc = setlocale(LC_ALL, NULL); setlocale(LC_ALL, ""); for (i = 0; i < argc; i++) { argv_copy[i] = Py_DecodeLocale(argv[i], NULL); @@ -81,8 +72,6 @@ Py_FrozenMain(int argc, char **argv) } } setlocale(LC_ALL, oldloc); - PyMem_RawFree(oldloc); - oldloc = NULL; #endif #ifdef MS_WINDOWS @@ -122,14 +111,6 @@ Py_FrozenMain(int argc, char **argv) else sts = 0; -#if PY_MAJOR_VERSION >= 3 - n = PyImport_ImportFrozenModule("_frozen_importlib"); - if (n == 0) - Py_FatalError("_frozen_importlib not frozen"); - if (n < 0) - PyErr_Print(); -#endif - if (inspect && isatty((int)fileno(stdin))) sts = PyRun_AnyFile(stdin, "") != 0; @@ -140,13 +121,15 @@ Py_FrozenMain(int argc, char **argv) #if PY_MAJOR_VERSION >= 3 error: - PyMem_RawFree(argv_copy); if (argv_copy2) { - for (i = 0; i < argc; i++) + for (i = 0; i < argc; i++) { +#if PY_MINOR_VERSION >= 4 PyMem_RawFree(argv_copy2[i]); - PyMem_RawFree(argv_copy2); +#else + PyMem_Free(argv_copy2[i]); +#endif + } } - PyMem_RawFree(oldloc); #endif return sts; } From 0002030e860eb62463a3f8b72571f2e189d95237 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 6 Dec 2016 02:32:08 +0100 Subject: [PATCH 049/424] deploy-ng: fix missing Py_DecodeLocale definition error --- pandatool/src/deploy-stub/deploy-stub.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 14fbe6fbd3..0c0215be90 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -9,6 +9,10 @@ #if PY_MAJOR_VERSION >= 3 #include + +#if PY_MINOR_VERSION < 5 +#define Py_DecodeLocale _Py_char2wchar +#endif #endif #ifdef MS_WINDOWS From bae19a203be8297ad9ea7eb4412a7e19d0691e98 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 8 Dec 2016 13:21:10 +0100 Subject: [PATCH 050/424] deploy-ng: changes to make it work on Windows --- direct/src/showutil/FreezeTool.py | 5 +- direct/src/showutil/dist.py | 8 +- makepanda/makepanda.py | 7 +- pandatool/src/deploy-stub/deploy-stub.c | 7 +- pandatool/src/deploy-stub/frozen_dllmain.c | 134 +++++++++++++++++++++ 5 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 pandatool/src/deploy-stub/frozen_dllmain.c diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 77f2be1a5a..1ebea45de1 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1616,7 +1616,10 @@ class Freezer: '-x %s to exclude the entire package.' % (moduleName, moduleName.split('.')[0])) # Build from pre-built binary stub - stub_path = os.path.join(os.path.dirname(ExecutionEnvironment.get_dtool_name()), '..', 'bin', 'deploy-stub') + dtool_path = Filename(ExecutionEnvironment.get_dtool_name()).to_os_specific() + stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', 'deploy-stub') + if sys.platform in ('win32', 'cygwin'): + stub_path += '.exe' with open(stub_path, 'rb') as f: stubbin = f.read() diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index dda2e9f3e7..7e1f60c0ee 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -45,9 +45,9 @@ class build(distutils.command.build.build): freezer.generateRuntimeFromStub(basename) # Copy Panda3D libs - dtool_dir = os.path.dirname(p3d.ExecutionEnvironment.get_dtool_name()) - libdir = os.path.join(dtool_dir, '..', 'lib') - src = os.path.join(libdir, '..', 'panda3d') + dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) + libdir = os.path.dirname(dtool_fn.to_os_specific()) + src = os.path.normpath(os.path.join(libdir, '..', 'panda3d')) dst = os.path.join(builddir, 'panda3d') distutils.dir_util.copy_tree(src, dst) @@ -59,7 +59,7 @@ class build(distutils.command.build.build): distutils.file_util.copy_file(source_path, target_path) # Copy etc - src = os.path.join(dtool_dir, '..', 'etc') + src = os.path.join(libdir, '..', 'etc') dst = os.path.join(builddir, 'etc') distutils.dir_util.copy_tree(src, dst) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index db99f4a483..8a3ad1d190 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6358,12 +6358,17 @@ if (PkgSkip("CONTRIB")==0 and not RUNTIME): if True: # TODO OPTS=['DIR:pandatool/src/deploy-stub', 'BUILDING:DEPLOYSTUB', 'PYTHON'] TargetAdd('deploy-stub.obj', opts=OPTS, input='deploy-stub.c') + if GetTarget() == 'windows': + TargetAdd('frozen_dllmain.obj', opts=OPTS, input='frozen_dllmain.c') + if GetTarget() == 'linux': # Setup rpath so libs can be found in the same directory as the deployed game LibName('DEPLOYSTUB', "-Wl,-rpath,\$ORIGIN") LibName('DEPLOYSTUB', "-Wl,-z,origin") TargetAdd('deploy-stub.exe', input='deploy-stub.obj') - TargetAdd('deploy-stub.exe', opts=['PYTHON', 'DEPLOYSTUB']) + if GetTarget() == 'windows': + TargetAdd('deploy-stub.exe', input='frozen_dllmain.obj') + TargetAdd('deploy-stub.exe', opts=['PYTHON', 'DEPLOYSTUB', 'NOICON']) # # Generate the models directory and samples directory diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 0c0215be90..4a08b554ff 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -20,6 +20,10 @@ extern void PyWinFreeze_ExeInit(void); extern void PyWinFreeze_ExeTerm(void); extern DL_IMPORT(int) PyImport_ExtendInittab(struct _inittab *newtab); + +static struct _inittab extensions[] = { + {0, 0}, +}; #endif static unsigned char *modblob = NULL; @@ -165,7 +169,8 @@ main(int argc, char *argv[]) { for (modidx = 0; modidx < nummods; ++modidx) { struct _frozen *moddef = &_PyImport_FrozenModules[modidx]; char *name = NULL, namebuf[256] = {0}; - unsigned int nsize, codeptr; + size_t nsize; + unsigned int codeptr; int codesize; // Name diff --git a/pandatool/src/deploy-stub/frozen_dllmain.c b/pandatool/src/deploy-stub/frozen_dllmain.c new file mode 100644 index 0000000000..3e374cf558 --- /dev/null +++ b/pandatool/src/deploy-stub/frozen_dllmain.c @@ -0,0 +1,134 @@ +/* FreezeDLLMain.cpp + +This is a DLLMain suitable for frozen applications/DLLs on +a Windows platform. + +The general problem is that many Python extension modules may define +DLL main functions, but when statically linked together to form +a frozen application, this DLLMain symbol exists multiple times. + +The solution is: +* Each module checks for a frozen build, and if so, defines its DLLMain + function as "__declspec(dllexport) DllMain%module%" + (eg, DllMainpythoncom, or DllMainpywintypes) + +* The frozen .EXE/.DLL links against this module, which provides + the single DllMain. + +* This DllMain attempts to locate and call the DllMain for each + of the extension modules. + +* This code also has hooks to "simulate" DllMain when used from + a frozen .EXE. + +At this stage, there is a static table of "possibly embedded modules". +This should change to something better, but it will work OK for now. + +Note that this scheme does not handle dependencies in the order +of DllMain calls - except it does call pywintypes first :-) + +As an example of how an extension module with a DllMain should be +changed, here is a snippet from the pythoncom extension module. + + // end of example code from pythoncom's DllMain.cpp + #ifndef BUILD_FREEZE + #define DLLMAIN DllMain + #define DLLMAIN_DECL + #else + #define DLLMAIN DllMainpythoncom + #define DLLMAIN_DECL __declspec(dllexport) + #endif + + extern "C" DLLMAIN_DECL + BOOL WINAPI DLLMAIN(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) + // end of example code from pythoncom's DllMain.cpp + +***************************************************************************/ +#include "windows.h" + +static char *possibleModules[] = { + "pywintypes", + "pythoncom", + "win32ui", + NULL, +}; + +BOOL CallModuleDllMain(char *modName, DWORD dwReason); + + +/* + Called by a frozen .EXE only, so that built-in extension + modules are initialized correctly +*/ +void PyWinFreeze_ExeInit(void) +{ + char **modName; + for (modName = possibleModules;*modName;*modName++) { +/* printf("Initialising '%s'\n", *modName); */ + CallModuleDllMain(*modName, DLL_PROCESS_ATTACH); + } +} + +/* + Called by a frozen .EXE only, so that built-in extension + modules are cleaned up +*/ +void PyWinFreeze_ExeTerm(void) +{ + // Must go backwards + char **modName; + for (modName = possibleModules+(sizeof(possibleModules) / sizeof(char *))-2; + modName >= possibleModules; + *modName--) { +/* printf("Terminating '%s'\n", *modName);*/ + CallModuleDllMain(*modName, DLL_PROCESS_DETACH); + } +} + +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + BOOL ret = TRUE; + switch (dwReason) { + case DLL_PROCESS_ATTACH: + { + char **modName; + for (modName = possibleModules;*modName;*modName++) { + BOOL ok = CallModuleDllMain(*modName, dwReason); + if (!ok) + ret = FALSE; + } + break; + } + case DLL_PROCESS_DETACH: + { + // Must go backwards + char **modName; + for (modName = possibleModules+(sizeof(possibleModules) / sizeof(char *))-2; + modName >= possibleModules; + *modName--) + CallModuleDllMain(*modName, DLL_PROCESS_DETACH); + break; + } + } + return ret; +} + +BOOL CallModuleDllMain(char *modName, DWORD dwReason) +{ + BOOL (WINAPI * pfndllmain)(HINSTANCE, DWORD, LPVOID); + + char funcName[255]; + HMODULE hmod = GetModuleHandleW(NULL); + strcpy(funcName, "_DllMain"); + strcat(funcName, modName); + strcat(funcName, "@12"); // stdcall convention. + pfndllmain = (BOOL (WINAPI *)(HINSTANCE, DWORD, LPVOID))GetProcAddress(hmod, funcName); + if (pfndllmain==NULL) { + /* No function by that name exported - then that module does + not appear in our frozen program - return OK + */ + return TRUE; + } + return (*pfndllmain)(hmod, dwReason, NULL); +} + From 2dab76131cba098ef7af25c8d1424bb4570fdfba Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 11 Dec 2016 19:38:14 +0100 Subject: [PATCH 051/424] deploy-ng: support nested extension modules; omit unused panda3d.x modules --- direct/src/showutil/FreezeTool.py | 18 ++++++------------ direct/src/showutil/dist.py | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 1ebea45de1..ec60c3ebdb 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1563,9 +1563,10 @@ class Freezer: if self.platform.startswith('win'): target = basename + '.exe' + modext = '.pyd' else: target = basename - + modext = '.so' # Generate export table. moduleBlob = bytes() @@ -1595,25 +1596,18 @@ class Freezer: # it is the former. extensionFilename = getattr(module, '__file__', None) - if extensionFilename or self.linkExtensionModules: + if extensionFilename: self.extras.append((moduleName, extensionFilename)) # If it is a submodule of a frozen module, Python will have # trouble importing it as a builtin module. Synthesize a frozen - # module that loads it as builtin. - if '.' in moduleName and self.linkExtensionModules: - code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec') + # module that loads it dynamically. + if '.' in moduleName: + code = compile('import sys;del sys.modules["%s"];import imp;imp.load_dynamic("%s", "%s%s")' % (moduleName, moduleName, moduleName, modext), moduleName, 'exec') code = marshal.dumps(code) moduleList.append(make_module_list_entry(code, codeOffset, moduleName, module)) moduleBlob += code codeOffset += len(code) - elif '.' in moduleName: - # Nothing we can do about this case except warn the user they - # are in for some trouble. - print('WARNING: Python cannot import extension modules under ' - 'frozen Python packages; %s will be inaccessible. ' - 'passing either -l to link in extension modules or use ' - '-x %s to exclude the entire package.' % (moduleName, moduleName.split('.')[0])) # Build from pre-built binary stub dtool_path = Filename(ExecutionEnvironment.get_dtool_name()).to_os_specific() diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 7e1f60c0ee..2f8e1298be 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -38,18 +38,28 @@ class build(distutils.command.build.build): # Create runtime freezer = FreezeTool.Freezer() freezer.addModule('__main__', filename=startfile) - freezer.excludeModule('panda3d') for exmod in self.distribution.exclude_modules: freezer.excludeModule(exmod) freezer.done(addStartupModules=True) freezer.generateRuntimeFromStub(basename) + # Copy extension modules + for module, source_path in freezer.extras: + if source_path is None: + # Built-in module. + continue + + # Rename panda3d/core.pyd to panda3d.core.pyd + basename = os.path.basename(source_path) + if '.' in module: + basename = module.rsplit('.', 1)[0] + '.' + basename + + target_path = os.path.join(builddir, basename) + distutils.file_util.copy_file(source_path, target_path) + # Copy Panda3D libs dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) libdir = os.path.dirname(dtool_fn.to_os_specific()) - src = os.path.normpath(os.path.join(libdir, '..', 'panda3d')) - dst = os.path.join(builddir, 'panda3d') - distutils.dir_util.copy_tree(src, dst) for item in os.listdir(libdir): if '.so.' in item or item.endswith('.dll') or item.endswith('.dylib') or 'libpandagl' in item: From 3ab9bde73dac90a273bae14de2571b824892c31a Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 18 Dec 2016 10:53:38 -0800 Subject: [PATCH 052/424] deploy-ng: Only build deploy-stub if Python is available --- makepanda/makepanda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 8a3ad1d190..7b3ff0c3a2 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6355,7 +6355,7 @@ if (PkgSkip("CONTRIB")==0 and not RUNTIME): # # DIRECTORY: pandatool/src/deploy-stub # -if True: # TODO +if PkgSkip("PYTHON") == 0: OPTS=['DIR:pandatool/src/deploy-stub', 'BUILDING:DEPLOYSTUB', 'PYTHON'] TargetAdd('deploy-stub.obj', opts=OPTS, input='deploy-stub.c') if GetTarget() == 'windows': From 7bf2b12f09dd1ea573d553a82b9f1c64e22407a3 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 18 Dec 2016 11:32:56 -0800 Subject: [PATCH 053/424] deploy-ng: Replace game_dir keyword with directories, files, and exclude_paths Also, mainfile has been replaced with startfile. --- direct/src/showutil/dist.py | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2f8e1298be..6c87dda467 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -12,10 +12,11 @@ import panda3d.core as p3d class Distribution(distutils.dist.Distribution): def __init__(self, attrs): - self.mainfile = 'main.py' - self.game_dir = 'game' + self.startfile = '' + self.directories = [] + self.files = [] + self.exclude_paths = [] self.exclude_modules = [] - self.extras = [] distutils.dist.Distribution.__init__(self, attrs) @@ -32,12 +33,10 @@ class build(distutils.command.build.build): distutils.dir_util.mkpath(builddir) basename = os.path.abspath(os.path.join(builddir, self.distribution.get_fullname())) - gamedir = self.distribution.game_dir - startfile = os.path.join(gamedir, self.distribution.mainfile) # Create runtime freezer = FreezeTool.Freezer() - freezer.addModule('__main__', filename=startfile) + freezer.addModule('__main__', filename=self.distribution.startfile) for exmod in self.distribution.exclude_modules: freezer.excludeModule(exmod) freezer.done(addStartupModules=True) @@ -76,26 +75,27 @@ class build(distutils.command.build.build): # Copy Game Files ignore_copy_list = [ '__pycache__', - self.distribution.mainfile, - ] + freezer.getAllModuleNames() + self.distribution.startfile, + ] + freezer.getAllModuleNames() + self.distribution.exclude_paths - for item in os.listdir(gamedir): - src = os.path.join(gamedir, item) - dst = os.path.join(builddir, item) + for copydir in self.distribution.directories: + for item in os.listdir(copydir): + src = os.path.join(copydir, item) + dst = os.path.join(builddir, item) - if item in ignore_copy_list: - print("skipping", src) - continue + if item in ignore_copy_list: + print("skipping", src) + continue - if os.path.isdir(src): - #print("Copy dir", src, dst) - distutils.dir_util.copy_tree(src, dst) - else: - #print("Copy file", src, dst) - distutils.file_util.copy_file(src, dst) + if os.path.isdir(src): + #print("Copy dir", src, dst) + distutils.dir_util.copy_tree(src, dst) + else: + #print("Copy file", src, dst) + distutils.file_util.copy_file(src, dst) # Copy extra files - for extra in self.distribution.extras: + for extra in self.distribution.files: if len(extra) == 2: src, dst = extra dst = os.path.join(builddir, dst) From 086e61f9c334146ae1ccb952edd4cf02c5786c5c Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 18 Dec 2016 11:43:38 -0800 Subject: [PATCH 054/424] deploy-ng: Replace startfile keyword with applications applications is a list of Application objects. The Application constructor takes two arguments: scriptname and runtimename. The scriptname argument is the path to the Python script that acts as the entry point to the application. The runtimename argument is the name that will be given to the frozen runtime. --- direct/src/showutil/dist.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 6c87dda467..5130c7d314 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1,3 +1,4 @@ +import collections import os import sys @@ -10,9 +11,13 @@ import distutils.file_util from direct.showutil import FreezeTool import panda3d.core as p3d + +Application = collections.namedtuple('Application', 'scriptname runtimename') + + class Distribution(distutils.dist.Distribution): def __init__(self, attrs): - self.startfile = '' + self.applications = [] self.directories = [] self.files = [] self.exclude_paths = [] @@ -32,15 +37,14 @@ class build(distutils.command.build.build): distutils.dir_util.remove_tree(builddir) distutils.dir_util.mkpath(builddir) - basename = os.path.abspath(os.path.join(builddir, self.distribution.get_fullname())) - # Create runtime - freezer = FreezeTool.Freezer() - freezer.addModule('__main__', filename=self.distribution.startfile) - for exmod in self.distribution.exclude_modules: - freezer.excludeModule(exmod) - freezer.done(addStartupModules=True) - freezer.generateRuntimeFromStub(basename) + for app in self.distribution.applications: + freezer = FreezeTool.Freezer() + freezer.addModule('__main__', filename=app.scriptname) + for exmod in self.distribution.exclude_modules: + freezer.excludeModule(exmod) + freezer.done(addStartupModules=True) + freezer.generateRuntimeFromStub(app.runtimename) # Copy extension modules for module, source_path in freezer.extras: @@ -75,8 +79,7 @@ class build(distutils.command.build.build): # Copy Game Files ignore_copy_list = [ '__pycache__', - self.distribution.startfile, - ] + freezer.getAllModuleNames() + self.distribution.exclude_paths + ] + freezer.getAllModuleNames() + self.distribution.exclude_paths + [i.scriptname for i in self.distribution.applications] for copydir in self.distribution.directories: for item in os.listdir(copydir): From ca036a56f254bf30715b93fbd3434874f8b24db5 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 18 Dec 2016 11:46:21 -0800 Subject: [PATCH 055/424] deploy-ng: Add setup.py to the asteroids sample --- samples/asteroids/setup.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 samples/asteroids/setup.py diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py new file mode 100644 index 0000000000..42feceb3b9 --- /dev/null +++ b/samples/asteroids/setup.py @@ -0,0 +1,8 @@ +from direct.showutil.dist import * + +setup( + name="asteroids", + directories=['.'], + exclude_paths=['build', 'setup.py'], + applications=[Application('main.py', 'asteroids')], +) From 2923af7d4440b596edbf82326634a49513b33f7e Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 18 Dec 2016 14:40:01 -0800 Subject: [PATCH 056/424] deploy-ng: Add bdist_panda3d command to create archives for each built platform Linux platforms use tar.gz and everything else (Windows, macOS) uses zip. Later we will want to add an option to control this. Also, bdist_panda3d currently auto-runs build. We will want an option to skip this build step for debug/testing purposes. --- direct/src/showutil/dist.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 5130c7d314..5a4842b98b 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -108,8 +108,40 @@ class build(distutils.command.build.build): distutils.file_util.copy_file(src, dst) +class bdist_panda3d(distutils.core.Command): + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + platforms = [p3d.PandaSystem.get_platform()] + build_base = os.path.join(os.getcwd(), 'build') + + self.run_command("build") + os.chdir(build_base) + + for platform in platforms: + build_dir = os.path.join(build_base, platform) + base_dir = self.distribution.get_name() + temp_dir = os.path.join(build_base, base_dir) + archive_format = 'gztar' if platform.startswith('linux') else 'zip' + basename = '{}_{}'.format(self.distribution.get_fullname(), platform) + + if (os.path.exists(temp_dir)): + distutils.dir_util.remove_tree(temp_dir) + distutils.dir_util.copy_tree(build_dir, temp_dir) + + distutils.archive_util.make_archive(basename, archive_format, root_dir=build_base, base_dir=base_dir) + + distutils.dir_util.remove_tree(temp_dir) + def setup(**attrs): attrs.setdefault("distclass", Distribution) commandClasses = attrs.setdefault("cmdclass", {}) commandClasses['build'] = build + commandClasses['bdist_panda3d'] = bdist_panda3d distutils.core.setup(**attrs) From 74a7d4ee48bb1c7182d56516b61d57452988a9ab Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 18 Dec 2016 15:02:49 -0800 Subject: [PATCH 057/424] deploy-ng: Remove Python version string from the filename of copied extension modules --- direct/src/showutil/dist.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 5a4842b98b..a92f610df2 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -57,6 +57,9 @@ class build(distutils.command.build.build): if '.' in module: basename = module.rsplit('.', 1)[0] + '.' + basename + # Remove python version string + basename = '.'.join([i for i in basename.split('.') if not i.startswith('cpython-')]) + target_path = os.path.join(builddir, basename) distutils.file_util.copy_file(source_path, target_path) From fa7f385bb8ab3fe71cb579d42635622fd453de47 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 28 Dec 2016 17:20:20 +0100 Subject: [PATCH 058/424] More work on integrating new input framework --- panda/src/device/evdevInputDevice.cxx | 15 ++++- panda/src/device/inputDevice.I | 56 +++++++++---------- panda/src/device/inputDevice.cxx | 36 +++++++++++- panda/src/device/inputDevice.h | 20 +++---- panda/src/display/graphicsWindow.cxx | 5 -- panda/src/display/graphicsWindowInputDevice.I | 12 +--- .../src/display/graphicsWindowInputDevice.cxx | 4 +- panda/src/display/graphicsWindowInputDevice.h | 5 -- panda/src/egldisplay/eglGraphicsWindow.cxx | 38 ------------- panda/src/egldisplay/eglGraphicsWindow.h | 1 - panda/src/putil/gamepadButton.cxx | 4 ++ panda/src/putil/gamepadButton.h | 2 + panda/src/x11display/x11GraphicsWindow.cxx | 14 ++--- panda/src/x11display/x11GraphicsWindow.h | 7 --- 14 files changed, 97 insertions(+), 122 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 39ba47b236..7a71e5f222 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -346,8 +346,16 @@ init_device() { int bi = 0; for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { - set_button_map(bi, map_button(i)); - //cerr << "Button " << bi << " is mapped by the driver to " << i << "\n"; + ButtonHandle mapped = map_button(i); + set_button_map(bi, mapped); + + if (mapped == ButtonHandle::none()) { + if (device_cat.is_debug()) { + device_cat.debug() << "Unmapped /dev/input/event" << _index + << " button " << bi << ": 0x" << hex << i << dec << "\n"; + } + } + if (test_bit(i, states)) { _buttons[bi].state = S_down; all_values_zero = false; @@ -735,6 +743,9 @@ map_button(int code) { } switch (code) { + case BTN_TRIGGER: + return GamepadButton::trigger(); + case BTN_A: return GamepadButton::action_a(); diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index a6094c8c69..b512b973a8 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -175,7 +175,7 @@ get_max_battery_level() const { * associated with a ButtonHandle even if their state is unknown. This number * may change as more buttons are discovered. */ -INLINE int InputDevice:: +INLINE size_t InputDevice:: get_num_buttons() const { LightMutexHolder holder(_lock); return _buttons.size(); @@ -192,10 +192,9 @@ get_num_buttons() const { * generate ButtonEvents when the buttons change state. */ INLINE void InputDevice:: -set_button_map(int index, ButtonHandle button) { +set_button_map(size_t index, ButtonHandle button) { LightMutexHolder holder(_lock); - nassertv(index >= 0); - if (index >= (int)_buttons.size()) { + if (index >= _buttons.size()) { _buttons.resize(index + 1, ButtonState()); } @@ -208,8 +207,8 @@ set_button_map(int index, ButtonHandle button) { * was associated. */ INLINE ButtonHandle InputDevice:: -get_button_map(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { +get_button_map(size_t index) const { + if (index < _buttons.size()) { return _buttons[index].handle; } else { return ButtonHandle::none(); @@ -221,8 +220,8 @@ get_button_map(int index) const { * currently known to be down, or false if it is up or unknown. */ INLINE bool InputDevice:: -get_button_state(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { +get_button_state(size_t index) const { + if (index < _buttons.size()) { return (_buttons[index].state == S_down); } else { return false; @@ -234,8 +233,8 @@ get_button_state(int index) const { * have never heard anything about this particular button. */ INLINE bool InputDevice:: -is_button_known(int index) const { - if (index >= 0 && index < (int)_buttons.size()) { +is_button_known(size_t index) const { + if (index < _buttons.size()) { return _buttons[index].state != S_unknown; } else { return false; @@ -248,12 +247,12 @@ is_button_known(int index) const { */ INLINE InputDevice::ButtonState InputDevice:: get_button(size_t index) const { - if (index >= 0 && index < (int)_buttons.size()) { + if (index < _buttons.size()) { return _buttons[index]; } else { device_cat.error() - << "Index " << index<< " was not found in the controls list\n"; - nassertr(false, ButtonState()); + << "Index " << index << " was not found in the controls list\n"; + return ButtonState(); } } @@ -263,21 +262,21 @@ get_button(size_t index) const { */ INLINE InputDevice::ButtonState InputDevice:: find_button(ButtonHandle handle) const { - for (int i; i < (int)_buttons.size(); i++) { + for (size_t i = 0; i < _buttons.size(); ++i) { if (_buttons[i].handle == handle) { return _buttons[i]; } } device_cat.error() << "Handle " << handle.get_name() << " was not found in the controls list\n"; - nassertr(false, ButtonState()); + return ButtonState(); } /** * Returns the number of analog controls known to the InputDevice. This number * may change as more controls are discovered. */ -INLINE int InputDevice:: +INLINE size_t InputDevice:: get_num_controls() const { return _controls.size(); } @@ -290,10 +289,9 @@ get_num_controls() const { * the various controls by index number. */ INLINE void InputDevice:: -set_control_map(int index, InputDevice::ControlAxis axis) { +set_control_map(size_t index, InputDevice::ControlAxis axis) { LightMutexHolder holder(_lock); - nassertv(index >= 0); - if (index >= (int)_controls.size()) { + if (index >= _controls.size()) { _controls.resize(index + 1, AnalogState()); } @@ -306,8 +304,8 @@ set_control_map(int index, InputDevice::ControlAxis axis) { * associated. */ INLINE InputDevice::ControlAxis InputDevice:: -get_control_map(int index) const { - if (index >= 0 && index < (int)_controls.size()) { +get_control_map(size_t index) const { + if (index < _controls.size()) { return _controls[index].axis; } else { return C_none; @@ -320,8 +318,8 @@ get_control_map(int index) const { * single control is -1.0 to 1.0. */ INLINE double InputDevice:: -get_control_state(int index) const { - if (index >= 0 && index < (int)_controls.size()) { +get_control_state(size_t index) const { + if (index < _controls.size()) { return _controls[index].state; } else { return 0.0; @@ -334,12 +332,12 @@ get_control_state(int index) const { */ INLINE InputDevice::AnalogState InputDevice:: get_control(size_t index) const { - if (index >= 0 && index < (int)_controls.size()) { + if (index < _controls.size()) { return _controls[index]; } else { device_cat.error() << "Index " << index<< " was not found in the controls list\n"; - nassertr(false, AnalogState()); + return AnalogState(); } } @@ -349,14 +347,14 @@ get_control(size_t index) const { */ INLINE InputDevice::AnalogState InputDevice:: find_control(InputDevice::ControlAxis axis) const { - for (int i; i < (int)_controls.size(); i++) { + for (size_t i = 0; i < _controls.size(); ++i) { if (_controls[i].axis == axis) { return _controls[i]; } } device_cat.error() << "Axis " << axis << " was not found in the controls list\n"; - nassertr(false, AnalogState()); + return AnalogState(); } /** @@ -364,8 +362,8 @@ find_control(InputDevice::ControlAxis axis) const { * if we have never heard anything about this particular control. */ INLINE bool InputDevice:: -is_control_known(int index) const { - if (index >= 0 && index < (int)_controls.size()) { +is_control_known(size_t index) const { + if (index < _controls.size()) { return _controls[index].known; } else { return false; diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 956a821bef..6a910de566 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -212,8 +212,8 @@ set_control_state(int index, double state) { device_cat.spam() << "Changed control " << index; - if (_controls[index].known != C_none) { - device_cat.spam(false) << " (" << _controls[index].known << ")"; + if (_controls[index].axis != C_none) { + device_cat.spam(false) << " (" << _controls[index].axis << ")"; } device_cat.spam(false) << " to " << state << "\n"; @@ -438,6 +438,14 @@ operator << (ostream &out, InputDevice::DeviceClass dc) { case InputDevice::DC_steering_wheel: out << "steering_wheel"; break; + + case InputDevice::DC_dance_pad: + out << "dance_pad"; + break; + + case InputDevice::DC_hmd: + out << "hmd"; + break; } return out; } @@ -488,6 +496,30 @@ operator << (ostream &out, InputDevice::ControlAxis axis) { case InputDevice::C_throttle: out << "throttle"; break; + + case InputDevice::C_rudder: + out << "rudder"; + break; + + case InputDevice::C_hat_x: + out << "hat_x"; + break; + + case InputDevice::C_hat_y: + out << "hat_y"; + break; + + case InputDevice::C_wheel: + out << "wheel"; + break; + + case InputDevice::C_accelerator: + out << "accelerator"; + break; + + case InputDevice::C_brake: + out << "brake"; + break; } return out; diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index a007fbb081..d964b80781 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -156,17 +156,17 @@ PUBLISHED: INLINE short get_battery_level() const; INLINE short get_max_battery_level() const; - INLINE int get_num_buttons() const; - INLINE void set_button_map(int index, ButtonHandle button); - INLINE ButtonHandle get_button_map(int index) const; - INLINE bool get_button_state(int index) const; - INLINE bool is_button_known(int index) const; + INLINE size_t get_num_buttons() const; + INLINE void set_button_map(size_t index, ButtonHandle button); + INLINE ButtonHandle get_button_map(size_t index) const; + INLINE bool get_button_state(size_t index) const; + INLINE bool is_button_known(size_t index) const; - INLINE int get_num_controls() const; - INLINE void set_control_map(int index, ControlAxis axis); - INLINE ControlAxis get_control_map(int index) const; - INLINE double get_control_state(int index) const; - INLINE bool is_control_known(int index) const; + INLINE size_t get_num_controls() const; + INLINE void set_control_map(size_t index, ControlAxis axis); + INLINE ControlAxis get_control_map(size_t index) const; + INLINE double get_control_state(size_t index) const; + INLINE bool is_control_known(size_t index) const; INLINE void set_vibration(double strong, double weak); diff --git a/panda/src/display/graphicsWindow.cxx b/panda/src/display/graphicsWindow.cxx index cb7960313e..d37d765d91 100644 --- a/panda/src/display/graphicsWindow.cxx +++ b/panda/src/display/graphicsWindow.cxx @@ -690,11 +690,6 @@ add_input_device(InputDevice *device) { LightMutexHolder holder(_input_lock); int index = (int)_input_devices.size(); _input_devices.push_back(device); - - if (device->is_of_type(GraphicsWindowInputDevice::get_class_type())) { - ((GraphicsWindowInputDevice *)device)->set_device_index(index); - } - return index; } diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index 9d89dc308e..ef97c1f974 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -15,17 +15,7 @@ * */ INLINE GraphicsWindowInputDevice:: -GraphicsWindowInputDevice() : _host(NULL) { -} - -/** - * Set the device index. This is reported in pointer events. The device - * index will be equal to the position of the GraphicsWindowInputDevice in the - * window's list. - */ -INLINE void GraphicsWindowInputDevice:: -set_device_index(int index) { - _device_index = index; +GraphicsWindowInputDevice() { } /** diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index 4f30561112..6e0a5a7dfe 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -28,8 +28,7 @@ TypeHandle GraphicsWindowInputDevice::_type_handle; */ GraphicsWindowInputDevice:: GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : - InputDevice(name, DC_virtual, flags), - _host(host) + InputDevice(name, DC_virtual, flags) { } @@ -74,7 +73,6 @@ GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) { */ void GraphicsWindowInputDevice:: operator = (const GraphicsWindowInputDevice ©) { - _host = copy._host; InputDevice::operator = (copy); } diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index 50bd03e01f..b5b6a98a46 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -39,8 +39,6 @@ public: void operator = (const GraphicsWindowInputDevice ©); ~GraphicsWindowInputDevice(); - INLINE void set_device_index(int index); - PUBLISHED: // The following interface is for the various kinds of GraphicsWindows to // record the data incoming on the device. @@ -61,9 +59,6 @@ PUBLISHED: INLINE void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); private: - GraphicsWindow *_host; - int _device_index; - typedef pset ButtonsHeld; ButtonsHeld _buttons_held; diff --git a/panda/src/egldisplay/eglGraphicsWindow.cxx b/panda/src/egldisplay/eglGraphicsWindow.cxx index b7e7d5dccf..b9118a3f93 100644 --- a/panda/src/egldisplay/eglGraphicsWindow.cxx +++ b/panda/src/egldisplay/eglGraphicsWindow.cxx @@ -55,44 +55,6 @@ eglGraphicsWindow:: ~eglGraphicsWindow() { } -/** - * Forces the pointer to the indicated position within the window, if - * possible. - * - * Returns true if successful, false on failure. This may fail if the mouse - * is not currently within the window, or if the API doesn't support this - * operation. - */ -bool eglGraphicsWindow:: -move_pointer(int device, int x, int y) { - // Note: this is not thread-safe; it should be called only from App. - // Probably not an issue. - if (device == 0) { - // Move the system mouse pointer. - if (!_properties.get_foreground() || - !_input_devices[0].get_pointer().get_in_window()) { - // If the window doesn't have input focus, or the mouse isn't currently - // within the window, forget it. - return false; - } - - const MouseData &md = _input_devices[0].get_pointer(); - if (!md.get_in_window() || md.get_x() != x || md.get_y() != y) { - XWarpPointer(_display, None, _xwindow, 0, 0, 0, 0, x, y); - _input_devices[0].set_pointer_in_window(x, y); - } - return true; - } else { - // Move a raw mouse. - if ((device < 1)||(device >= _input_devices.size())) { - return false; - } - _input_devices[device].set_pointer_in_window(x, y); - return true; - } -} - - /** * This function will be called within the draw thread before beginning * rendering for a given frame. It should do whatever setup is required, and diff --git a/panda/src/egldisplay/eglGraphicsWindow.h b/panda/src/egldisplay/eglGraphicsWindow.h index 508365cc65..e936a97cef 100644 --- a/panda/src/egldisplay/eglGraphicsWindow.h +++ b/panda/src/egldisplay/eglGraphicsWindow.h @@ -33,7 +33,6 @@ public: GraphicsOutput *host); virtual ~eglGraphicsWindow(); - virtual bool move_pointer(int device, int x, int y); virtual bool begin_frame(FrameMode mode, Thread *current_thread); virtual void end_frame(FrameMode mode, Thread *current_thread); virtual void end_flip(); diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index ba7fe41b5a..7ddbc33b17 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -47,6 +47,8 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(action_z) DEFINE_GAMEPAD_BUTTON_HANDLE(action_1) DEFINE_GAMEPAD_BUTTON_HANDLE(action_2) +DEFINE_GAMEPAD_BUTTON_HANDLE(trigger) + /** * This is intended to be called only once, by the static initialization * performed in config_util.cxx. @@ -81,4 +83,6 @@ init_gamepad_buttons() { ButtonRegistry::ptr()->register_button(_action_1, "action_1"); ButtonRegistry::ptr()->register_button(_action_2, "action_2"); + + ButtonRegistry::ptr()->register_button(_trigger, "trigger"); } diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index 9a8597a6b9..0edbbad99b 100644 --- a/panda/src/putil/gamepadButton.h +++ b/panda/src/putil/gamepadButton.h @@ -53,6 +53,8 @@ PUBLISHED: static ButtonHandle action_1(); static ButtonHandle action_2(); + static ButtonHandle trigger(); + public: static void init_gamepad_buttons(); }; diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 43cff8e061..949d4841d0 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -131,14 +131,13 @@ move_pointer(int device, int x, int y) { // Probably not an issue. if (device == 0) { // Move the system mouse pointer. - if (!_properties.get_foreground() || - !_input->get_pointer().get_in_window()) { + PointerData md = _input->get_pointer(); + if (!_properties.get_foreground() || !md.get_in_window()) { // If the window doesn't have input focus, or the mouse isn't currently // within the window, forget it. return false; } - const MouseData &md = _input->get_pointer(); if (!md.get_in_window() || md.get_x() != x || md.get_y() != y) { if (!_dga_mouse_enabled) { XWarpPointer(_display, None, _xwindow, 0, 0, 0, 0, x, y); @@ -147,12 +146,8 @@ move_pointer(int device, int x, int y) { } return true; } else { - // Move a raw mouse. - if (device < 1 || device >= _input_devices.size()) { - return false; - } - //_input_devices[device]->set_pointer_in_window(x, y); - return true; + // Can't move a raw mouse. + return false; } } @@ -1236,6 +1231,7 @@ setup_colormap(XVisualInfo *visual) { /** * Adds raw mice to the _input_devices list. + * @deprecated obtain raw devices via the device manager instead. */ void x11GraphicsWindow:: open_raw_mice() { diff --git a/panda/src/x11display/x11GraphicsWindow.h b/panda/src/x11display/x11GraphicsWindow.h index 1400456d3b..c452622d03 100644 --- a/panda/src/x11display/x11GraphicsWindow.h +++ b/panda/src/x11display/x11GraphicsWindow.h @@ -102,13 +102,6 @@ protected: Bool _override_redirect; Atom _wm_delete_window; - struct MouseDeviceInfo { - int _fd; - int _input_device_index; - string _io_buffer; - }; - pvector _mouse_device_info; - public: static TypeHandle get_class_type() { return _type_handle; From 6251a1cfc7b678650bca4ef68ea3b50c4b59f065 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Fri, 30 Dec 2016 11:56:29 -0800 Subject: [PATCH 059/424] deploy-ng: Make sure the runtime is created in the build directory We used to build in the current directory and copy to the build directory, which resulted in two copies of the runtime. --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index a92f610df2..2e75d47aab 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -44,7 +44,7 @@ class build(distutils.command.build.build): for exmod in self.distribution.exclude_modules: freezer.excludeModule(exmod) freezer.done(addStartupModules=True) - freezer.generateRuntimeFromStub(app.runtimename) + freezer.generateRuntimeFromStub(os.path.join(builddir, app.runtimename)) # Copy extension modules for module, source_path in freezer.extras: From 5af7136aa2f27f34d9ad6c5a58ba8e3bbaaac8f3 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 2 Jan 2017 19:19:00 -0800 Subject: [PATCH 060/424] deploy-ng: Support copying panda3d files from wheels instead of the current environment Note: Extension modules are still coming from the current environment. We'll need to sort this out before we get true cross-platform building. Also, we need a proper interface for specifying platforms/wheels. At the moment, a wheels parameter is supplied to setup() and platforms are determined based on the wheels in this list. --- direct/src/showutil/FreezeTool.py | 11 +---- direct/src/showutil/dist.py | 78 +++++++++++++++++++++++++------ 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index ec60c3ebdb..c82bfe25eb 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1545,7 +1545,7 @@ class Freezer: return target - def generateRuntimeFromStub(self, basename): + def generateRuntimeFromStub(self, basename, stub_file): def make_module_list_entry(code, offset, modulename, module): size = len(code) if getattr(module, "__path__", None): @@ -1610,15 +1610,8 @@ class Freezer: codeOffset += len(code) # Build from pre-built binary stub - dtool_path = Filename(ExecutionEnvironment.get_dtool_name()).to_os_specific() - stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', 'deploy-stub') - if sys.platform in ('win32', 'cygwin'): - stub_path += '.exe' - with open(stub_path, 'rb') as f: - stubbin = f.read() - with open(target, 'wb') as f: - f.write(stubbin) + f.write(stub_file.read()) listoffset = f.tell() for mod in moduleList: f.write(mod) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2e75d47aab..569f3a0e69 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1,6 +1,7 @@ import collections import os import sys +import zipfile import distutils.command.build import distutils.core @@ -22,21 +23,54 @@ class Distribution(distutils.dist.Distribution): self.files = [] self.exclude_paths = [] self.exclude_modules = [] + self.wheels = [] distutils.dist.Distribution.__init__(self, attrs) +# TODO replace with Packager +def find_packages(whlfile): + if whlfile is None: + dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) + libdir = os.path.dirname(dtool_fn.to_os_specific()) + filelist = [os.path.join(libdir, i) for i in os.listdir(libdir)] + else: + filelist = whlfile.namelist() + + return [i for i in filelist if '.so.' in i or i.endswith('.dll') or i.endswith('.dylib') or 'libpandagl' in i] + + class build(distutils.command.build.build): def run(self): distutils.command.build.build.run(self) - platforms = [p3d.PandaSystem.get_platform()] + if not self.distribution.wheels: + platforms = {p3d.PandaSystem.get_platform(): None} + else: + platforms = { + whl.split('-')[-1].replace('.whl', ''): whl + for whl in self.distribution.wheels + } - for platform in platforms: + for platform, whl in platforms.items(): builddir = os.path.join(self.build_base, platform) if os.path.exists(builddir): distutils.dir_util.remove_tree(builddir) distutils.dir_util.mkpath(builddir) + if whl is not None: + whlfile = zipfile.ZipFile(whl) + stub_path = 'panda3d_tools/deploy-stub' + if platform.startswith('win'): + stub_path += '.exe' + stub_file = whlfile.open(stub_path) + else: + dtool_path = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()).to_os_specific() + stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', 'deploy-stub') + if platform.startswith('win'): + stub_path += '.exe' + stub_file = open(stub_path, 'rb') + + # Create runtime for app in self.distribution.applications: freezer = FreezeTool.Freezer() @@ -44,7 +78,8 @@ class build(distutils.command.build.build): for exmod in self.distribution.exclude_modules: freezer.excludeModule(exmod) freezer.done(addStartupModules=True) - freezer.generateRuntimeFromStub(os.path.join(builddir, app.runtimename)) + freezer.generateRuntimeFromStub(os.path.join(builddir, app.runtimename), stub_file) + stub_file.close() # Copy extension modules for module, source_path in freezer.extras: @@ -63,21 +98,34 @@ class build(distutils.command.build.build): target_path = os.path.join(builddir, basename) distutils.file_util.copy_file(source_path, target_path) - # Copy Panda3D libs - dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) - libdir = os.path.dirname(dtool_fn.to_os_specific()) + # Find Panda3D libs + libs = find_packages(whlfile if whl is not None else None) - for item in os.listdir(libdir): - if '.so.' in item or item.endswith('.dll') or item.endswith('.dylib') or 'libpandagl' in item: - source_path = os.path.join(libdir, item) - target_path = os.path.join(builddir, item) + # Copy Panda3D files + etcdir = os.path.join(builddir, 'etc') + if whl is None: + # Libs + for lib in libs: + target_path = os.path.join(builddir, os.path.basename(lib)) if not os.path.islink(source_path): - distutils.file_util.copy_file(source_path, target_path) + distutils.file_util.copy_file(lib, target_path) - # Copy etc - src = os.path.join(libdir, '..', 'etc') - dst = os.path.join(builddir, 'etc') - distutils.dir_util.copy_tree(src, dst) + # etc + dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) + libdir = os.path.dirname(dtool_fn.to_os_specific()) + src = os.path.join(libdir, '..', 'etc') + distutils.dir_util.copy_tree(src, etcdir) + else: + distutils.dir_util.mkpath(etcdir) + + # Combine prc files with libs and copy the whole list + panda_files = libs + [i for i in whlfile.namelist() if i.endswith('.prc')] + for pf in panda_files: + dstdir = etcdir if pf.endswith('.prc') else builddir + target_path = os.path.join(dstdir, os.path.basename(pf)) + print("copying {} -> {}".format(os.path.join(whl, pf), target_path)) + with open(target_path, 'wb') as f: + f.write(whlfile.read(pf)) # Copy Game Files ignore_copy_list = [ From 6b92d349c9751f359b0a1aa18b865eeb7d93a034 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 3 Jan 2017 20:17:12 -0800 Subject: [PATCH 061/424] deploy-ng: Add wheel files to sys.path so modulefinder can pick up the files --- direct/src/showutil/dist.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 569f3a0e69..2acdaf4f8a 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -57,12 +57,20 @@ class build(distutils.command.build.build): distutils.dir_util.remove_tree(builddir) distutils.dir_util.mkpath(builddir) + whldir = os.path.join(self.build_base, '__whl_cache__') + if os.path.exists(whldir): + distutils.dir_util.remove_tree(whldir) + if whl is not None: whlfile = zipfile.ZipFile(whl) stub_path = 'panda3d_tools/deploy-stub' if platform.startswith('win'): stub_path += '.exe' stub_file = whlfile.open(stub_path) + + # Add whl files to the path so they are picked up by modulefinder + whlfile.extractall(whldir) + sys.path.insert(0, whldir) else: dtool_path = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()).to_os_specific() stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', 'deploy-stub') @@ -158,6 +166,10 @@ class build(distutils.command.build.build): dst = builddir distutils.file_util.copy_file(src, dst) + # Cleanup whl directory + if os.path.exists(whldir): + distutils.dir_util.remove_tree(whldir) + class bdist_panda3d(distutils.core.Command): user_options = [] From 13c41bcf7d7a60431cba52db2f36106dad15808f Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 4 Jan 2017 19:57:51 +0100 Subject: [PATCH 062/424] FreezeTool: find modules in zip files, allow custom suffixes list --- direct/src/showutil/FreezeTool.py | 146 +++++++++++++++++++++++------- 1 file changed, 112 insertions(+), 34 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index c82bfe25eb..fb346b1610 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -10,6 +10,7 @@ import platform import struct from io import StringIO import distutils.sysconfig as sysconf +import zipfile # Temporary (?) try..except to protect against unbuilt p3extend_frozen. try: @@ -1661,51 +1662,128 @@ class Freezer: return True class PandaModuleFinder(modulefinder.ModuleFinder): - """ We subclass ModuleFinder here, to add functionality for - finding the libpandaexpress etc. modules that interrogate - produces. """ def __init__(self, *args, **kw): + """ + :param path: search path to look on, defaults to sys.path + :param suffixes: defaults to imp.get_suffixes() + :param excludes: a list of modules to exclude + :param debug: an integer indicating the level of verbosity + """ + + self.suffixes = kw.pop('suffixes', imp.get_suffixes()) + modulefinder.ModuleFinder.__init__(self, *args, **kw) - def find_module(self, name, path, *args, **kwargs): + # Make sure we don't open a .whl/.zip file more than once. + self._zip_files = {} + + def _open_file(self, path, mode): + """ Opens a module at the given path, which may contain a zip file. + Returns None if the module could not be found. """ + + if os.path.isfile(path): + return open(path, mode) + + # Is there a zip file along the path? + dir, dirname = os.path.split(path) + fn = dirname + while dirname: + if os.path.isfile(dir): + # Okay, this is actually a file. Is it a zip file? + if dir in self._zip_files: + # Yes, and we've previously opened this. + zip = self._zip_files[dir] + elif zipfile.is_zipfile(dir): + zip = zipfile.ZipFile(dir) + self._zip_files[dir] = zip + else: + # It's a different kind of file. Stop looking. + return None + + try: + fp = zip.open(fn, 'r') + except KeyError: + return None + + if sys.version_info >= (3, 0) and 'b' not in mode: + return TextIOWrapper(fp) + return fp + + # Look at the parent directory. + dir, dirname = os.path.split(dir) + fn = os.path.join(dirname, fn) + + return None + + def find_module(self, name, path=None, parent=None): + """ Finds a module with the indicated name on the given search path + (or self.path if None). Returns a tuple like (fp, path, stuff), where + stuff is a tuple like (suffix, mode, type). """ + if imp.is_frozen(name): # Don't pick up modules that are frozen into p3dpython. raise ImportError("'%s' is a frozen module" % (name)) - try: - return modulefinder.ModuleFinder.find_module(self, name, path, *args, **kwargs) - except ImportError: - # It wasn't found through the normal channels. Maybe it's - # one of ours, or maybe it's frozen? - if path: - # Only if we're not looking on a particular path, - # though. - raise + if parent is not None: + fullname = parent.__name__+'.'+name + else: + fullname = name + if fullname in self.excludes: + raise ImportError(name) + # If no search path is given, look for a built-in module. + if path is None: + if name in sys.builtin_module_names: + return (None, None, ('', '', imp.C_BUILTIN)) + + path = self.path + + # Look for the module on the search path. + for dir_path in path: + basename = os.path.join(dir_path, name.split('.')[-1]) + + # Look for recognized extensions. + for stuff in self.suffixes: + suffix, mode, type = stuff + fp = self._open_file(basename + suffix, mode) + if fp: + return (fp, basename + suffix, stuff) + + # Consider a package, i.e. a directory containing __init__.py. + for suffix, mode, type in self.suffixes: + init = os.path.join(basename, '__init__' + suffix) + if self._open_file(init, mode): + return (None, basename, ('', '', imp.PKG_DIRECTORY)) + + # It wasn't found through the normal channels. Maybe it's one of + # ours, or maybe it's frozen? + if not path: + # Only if we're not looking on a particular path, though. if p3extend_frozen and p3extend_frozen.is_frozen_module(name): # It's a frozen module. return (None, name, ('', '', imp.PY_FROZEN)) - message = "DLL loader cannot find %s." % (name) - raise ImportError(message) + raise ImportError(name) - def load_module(self, fqname, fp, pathname, file_info): - suffix, mode, type = file_info - - if type == imp.PY_FROZEN: - # It's a frozen module. - co, isPackage = p3extend_frozen.get_frozen_module_code(pathname) - m = self.add_module(fqname) - m.__file__ = '' - if isPackage: - m.__path__ = [pathname] - co = marshal.loads(co) - if self.replace_paths: - co = self.replace_paths_in_code(co) - m.__code__ = co - self.scan_code(co, m) - self.msgout(2, "load_module ->", m) - return m - - return modulefinder.ModuleFinder.load_module(self, fqname, fp, pathname, (suffix, mode, type)) + def find_all_submodules(self, m): + # Overridden so that we can define our own suffixes. + if not m.__path__: + return + modules = {} + for dir in m.__path__: + try: + names = os.listdir(dir) + except OSError: + self.msg(2, "can't list directory", dir) + continue + for name in names: + mod = None + for suff in self.suffixes: + n = len(suff) + if name[-n:] == suff: + mod = name[:-n] + break + if mod and mod != "__init__": + modules[mod] = mod + return modules.keys() From 1387f22200f3c58c376c8b2f701361e002c81293 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 4 Jan 2017 20:19:04 +0100 Subject: [PATCH 063/424] FreezeTool: add missing import. --- direct/src/showutil/FreezeTool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index fb346b1610..78bc993f3a 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -8,7 +8,7 @@ import marshal import imp import platform import struct -from io import StringIO +from io import StringIO, TextIOWrapper import distutils.sysconfig as sysconf import zipfile From db2cbd6e3a469b2a2412b0ebf71e447669db344c Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 4 Jan 2017 17:53:41 -0800 Subject: [PATCH 064/424] deploy-ng: Add whls to sys.path instead of extracting them --- direct/src/showutil/dist.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2acdaf4f8a..63911ecebf 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -69,8 +69,7 @@ class build(distutils.command.build.build): stub_file = whlfile.open(stub_path) # Add whl files to the path so they are picked up by modulefinder - whlfile.extractall(whldir) - sys.path.insert(0, whldir) + sys.path.insert(0, whl) else: dtool_path = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()).to_os_specific() stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', 'deploy-stub') @@ -104,7 +103,15 @@ class build(distutils.command.build.build): basename = '.'.join([i for i in basename.split('.') if not i.startswith('cpython-')]) target_path = os.path.join(builddir, basename) - distutils.file_util.copy_file(source_path, target_path) + if '.whl/' in source_path: + # This was found in a wheel, extract it + wf = source_path.split('.whl/')[-1] + print("copying {} -> {}".format(os.path.join(whl, wf), target_path)) + with open(target_path, 'wb') as f: + f.write(whlfile.read(wf)) + else: + # Regular file, copy it + distutils.file_util.copy_file(source_path, target_path) # Find Panda3D libs libs = find_packages(whlfile if whl is not None else None) From c51afaf0cf72e6c9335567f7f41357b91e4aceeb Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 4 Jan 2017 19:01:37 -0800 Subject: [PATCH 065/424] deploy-ng: Support getting whl files via pip The samples/asteriods/wheels directory and requirements.txt work-around can be removed when we can grab py3 whls from PyPI. --- direct/src/showutil/dist.py | 74 ++++++++++++++++++----------- samples/asteroids/requirements.txt | 2 + samples/asteroids/setup.py | 1 + samples/asteroids/wheels/readme.txt | 1 + 4 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 samples/asteroids/requirements.txt create mode 100644 samples/asteroids/wheels/readme.txt diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 63911ecebf..eb6bea40aa 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1,5 +1,6 @@ import collections import os +import pip import sys import zipfile @@ -23,7 +24,7 @@ class Distribution(distutils.dist.Distribution): self.files = [] self.exclude_paths = [] self.exclude_modules = [] - self.wheels = [] + self.deploy_platforms = [] distutils.dist.Distribution.__init__(self, attrs) @@ -42,34 +43,52 @@ def find_packages(whlfile): class build(distutils.command.build.build): def run(self): distutils.command.build.build.run(self) - if not self.distribution.wheels: - platforms = {p3d.PandaSystem.get_platform(): None} + print(self.distribution.deploy_platforms) + if not self.distribution.deploy_platforms: + platforms = [p3d.PandaSystem.get_platform()] + use_wheels = False else: - platforms = { - whl.split('-')[-1].replace('.whl', ''): whl - for whl in self.distribution.wheels - } + platforms = self.distribution.deploy_platforms + use_wheels = True + print("Building platforms: {}".format(','.join(platforms))) - for platform, whl in platforms.items(): + for platform in platforms: builddir = os.path.join(self.build_base, platform) - if os.path.exists(builddir): - distutils.dir_util.remove_tree(builddir) - distutils.dir_util.mkpath(builddir) + if not os.path.exists(builddir): + distutils.dir_util.mkpath(builddir) - whldir = os.path.join(self.build_base, '__whl_cache__') - if os.path.exists(whldir): - distutils.dir_util.remove_tree(whldir) + if use_wheels: + whldir = os.path.join(self.build_base, '__whl_cache__') - if whl is not None: - whlfile = zipfile.ZipFile(whl) + pip.main(args=[ + 'download', + '-d', whldir, + '-r', 'requirements.txt', + '--only-binary', ':all:', + '--platform', platform, + ]) + + wheelpaths = [os.path.join(whldir,i) for i in os.listdir(whldir) if platform in i] + + p3dwhl = None + for whl in wheelpaths: + if 'panda3d-' in whl: + p3dwhlfn = whl + p3dwhl = zipfile.ZipFile(p3dwhlfn) + break + else: + raise RuntimeError("Missing panda3d wheel") + + whlfiles = {whl: zipfile.ZipFile(whl) for whl in wheelpaths} stub_path = 'panda3d_tools/deploy-stub' if platform.startswith('win'): stub_path += '.exe' - stub_file = whlfile.open(stub_path) + stub_file = p3dwhl.open(stub_path) # Add whl files to the path so they are picked up by modulefinder - sys.path.insert(0, whl) + for whl in wheelpaths: + sys.path.insert(0, whl) else: dtool_path = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()).to_os_specific() stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', 'deploy-stub') @@ -105,7 +124,10 @@ class build(distutils.command.build.build): target_path = os.path.join(builddir, basename) if '.whl/' in source_path: # This was found in a wheel, extract it - wf = source_path.split('.whl/')[-1] + whl, wf = source_path.split('.whl/') + whl += '.whl' + print(whl, source_path) + whlfile = whlfiles[whl] print("copying {} -> {}".format(os.path.join(whl, wf), target_path)) with open(target_path, 'wb') as f: f.write(whlfile.read(wf)) @@ -114,11 +136,11 @@ class build(distutils.command.build.build): distutils.file_util.copy_file(source_path, target_path) # Find Panda3D libs - libs = find_packages(whlfile if whl is not None else None) + libs = find_packages(p3dwhl if use_wheels else None) # Copy Panda3D files etcdir = os.path.join(builddir, 'etc') - if whl is None: + if not use_wheels: # Libs for lib in libs: target_path = os.path.join(builddir, os.path.basename(lib)) @@ -134,13 +156,13 @@ class build(distutils.command.build.build): distutils.dir_util.mkpath(etcdir) # Combine prc files with libs and copy the whole list - panda_files = libs + [i for i in whlfile.namelist() if i.endswith('.prc')] + panda_files = libs + [i for i in p3dwhl.namelist() if i.endswith('.prc')] for pf in panda_files: dstdir = etcdir if pf.endswith('.prc') else builddir target_path = os.path.join(dstdir, os.path.basename(pf)) - print("copying {} -> {}".format(os.path.join(whl, pf), target_path)) + print("copying {} -> {}".format(os.path.join(p3dwhlfn, pf), target_path)) with open(target_path, 'wb') as f: - f.write(whlfile.read(pf)) + f.write(p3dwhl.read(pf)) # Copy Game Files ignore_copy_list = [ @@ -173,10 +195,6 @@ class build(distutils.command.build.build): dst = builddir distutils.file_util.copy_file(src, dst) - # Cleanup whl directory - if os.path.exists(whldir): - distutils.dir_util.remove_tree(whldir) - class bdist_panda3d(distutils.core.Command): user_options = [] diff --git a/samples/asteroids/requirements.txt b/samples/asteroids/requirements.txt new file mode 100644 index 0000000000..71b51750d6 --- /dev/null +++ b/samples/asteroids/requirements.txt @@ -0,0 +1,2 @@ +-f ./wheels +panda3d diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 42feceb3b9..a0e7c0474f 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -5,4 +5,5 @@ setup( directories=['.'], exclude_paths=['build', 'setup.py'], applications=[Application('main.py', 'asteroids')], + deploy_platforms=['linux_x86_64'], ) diff --git a/samples/asteroids/wheels/readme.txt b/samples/asteroids/wheels/readme.txt new file mode 100644 index 0000000000..d0ea2e0941 --- /dev/null +++ b/samples/asteroids/wheels/readme.txt @@ -0,0 +1 @@ +Put whl files in this folder to get picked up by the build step From e85a2aa14055a546410d7131d1666891f76f60de Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 4 Jan 2017 19:30:00 -0800 Subject: [PATCH 066/424] deploy-ng: Remove some debug prints --- direct/src/showutil/dist.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index eb6bea40aa..12bda7823a 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -43,7 +43,6 @@ def find_packages(whlfile): class build(distutils.command.build.build): def run(self): distutils.command.build.build.run(self) - print(self.distribution.deploy_platforms) if not self.distribution.deploy_platforms: platforms = [p3d.PandaSystem.get_platform()] use_wheels = False @@ -126,7 +125,6 @@ class build(distutils.command.build.build): # This was found in a wheel, extract it whl, wf = source_path.split('.whl/') whl += '.whl' - print(whl, source_path) whlfile = whlfiles[whl] print("copying {} -> {}".format(os.path.join(whl, wf), target_path)) with open(target_path, 'wb') as f: From 31136124c7529093476b5eb8b6fcb66f53f2b0ea Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 6 Jan 2017 01:42:00 +0100 Subject: [PATCH 067/424] deploy-ng: fix issues with Python 3 / Windows / Unicode, add deploy-stubw --- direct/src/showutil/FreezeTool.py | 8 +++- makepanda/makepanda.py | 23 ++++++----- pandatool/src/deploy-stub/deploy-stub.c | 53 +++++++++++++++++++------ 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 78bc993f3a..1c07fdbda4 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -32,6 +32,7 @@ isDebugBuild = (python.lower().endswith('_d')) # must be frozen in any main.exe. startupModules = [ 'encodings.cp1252', 'encodings.latin_1', 'encodings.utf_8', + 'encodings.mbcs', 'encodings.cp850', ] if sys.version_info >= (3, 0): startupModules += ['io', 'marshal', 'importlib.machinery', 'importlib.util'] @@ -1683,7 +1684,10 @@ class PandaModuleFinder(modulefinder.ModuleFinder): Returns None if the module could not be found. """ if os.path.isfile(path): - return open(path, mode) + if sys.version_info >= (3, 0) and 'b' not in mode: + return open(path, mode, encoding='utf8') + else: + return open(path, mode) # Is there a zip file along the path? dir, dirname = os.path.split(path) @@ -1707,7 +1711,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): return None if sys.version_info >= (3, 0) and 'b' not in mode: - return TextIOWrapper(fp) + return TextIOWrapper(fp, encoding='utf8') return fp # Look at the parent directory. diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index a93e12fdf4..a960061f88 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2661,16 +2661,16 @@ p3d_init = """"Python bindings for the Panda3D libraries" if GetTarget() == 'windows': p3d_init += """ -import os +if '__file__' in locals(): + import os -bindir = os.path.join(os.path.dirname(__file__), '..', 'bin') -if os.path.isfile(os.path.join(bindir, 'libpanda.dll')): - if not os.environ.get('PATH'): - os.environ['PATH'] = bindir - else: - os.environ['PATH'] = bindir + os.pathsep + os.environ['PATH'] - -del os, bindir + bindir = os.path.join(os.path.dirname(__file__), '..', 'bin') + if os.path.isfile(os.path.join(bindir, 'libpanda.dll')): + if not os.environ.get('PATH'): + os.environ['PATH'] = bindir + else: + os.environ['PATH'] = bindir + os.pathsep + os.environ['PATH'] + del os, bindir """ if not PkgSkip("PYTHON"): @@ -6384,6 +6384,11 @@ if PkgSkip("PYTHON") == 0: TargetAdd('deploy-stub.exe', input='frozen_dllmain.obj') TargetAdd('deploy-stub.exe', opts=['PYTHON', 'DEPLOYSTUB', 'NOICON']) + if GetTarget() == 'windows': + TargetAdd('deploy-stubw.exe', input='deploy-stub.obj') + TargetAdd('deploy-stubw.exe', input='frozen_dllmain.obj') + TargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) + # # Generate the models directory and samples directory # diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 4a08b554ff..3a79fd6942 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -16,29 +16,37 @@ #endif #ifdef MS_WINDOWS +#define WIN32_LEAN_AND_MEAN +#include + extern void PyWinFreeze_ExeInit(void); extern void PyWinFreeze_ExeTerm(void); -extern DL_IMPORT(int) PyImport_ExtendInittab(struct _inittab *newtab); - static struct _inittab extensions[] = { {0, 0}, }; + +#if PY_MAJOR_VERSION >= 3 +#define WIN_UNICODE +#endif #endif static unsigned char *modblob = NULL; /* Main program */ -int -Py_FrozenMain(int argc, char **argv) +#ifdef WIN_UNICODE +int Py_FrozenMain(int argc, wchar_t **argv) +#else +int Py_FrozenMain(int argc, char **argv) +#endif { char *p; int n, sts = 1; int inspect = 0; int unbuffered = 0; -#if PY_MAJOR_VERSION >= 3 +#if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE) int i; char *oldloc; wchar_t **argv_copy = NULL; @@ -66,7 +74,7 @@ Py_FrozenMain(int argc, char **argv) setbuf(stderr, (char *)NULL); } -#if PY_MAJOR_VERSION >= 3 +#if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE) oldloc = setlocale(LC_ALL, NULL); setlocale(LC_ALL, ""); for (i = 0; i < argc; i++) { @@ -87,7 +95,7 @@ Py_FrozenMain(int argc, char **argv) #endif /* MS_WINDOWS */ if (argc >= 1) { -#if PY_MAJOR_VERSION >= 3 +#if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE) Py_SetProgramName(argv_copy[0]); #else Py_SetProgramName(argv[0]); @@ -103,7 +111,7 @@ Py_FrozenMain(int argc, char **argv) fprintf(stderr, "Python %s\n%s\n", Py_GetVersion(), Py_GetCopyright()); -#if PY_MAJOR_VERSION >= 3 +#if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE) PySys_SetArgv(argc, argv_copy); #else PySys_SetArgv(argc, argv); @@ -127,7 +135,7 @@ Py_FrozenMain(int argc, char **argv) #endif Py_Finalize(); -#if PY_MAJOR_VERSION >= 3 +#if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE) error: if (argv_copy2) { for (i = 0; i < argc; i++) { @@ -143,11 +151,22 @@ error: } -int -main(int argc, char *argv[]) { +#if defined(_WIN32) && PY_MAJOR_VERSION >= 3 +int wmain(int argc, wchar_t *argv[]) { +#else +int main(int argc, char *argv[]) { +#endif struct _frozen *_PyImport_FrozenModules; unsigned int listoff, modsoff, fsize, modsize, listsize, nummods, modidx; - FILE *runtime = fopen(argv[0], "rb"); + FILE *runtime; + +#ifdef _WIN32 + wchar_t buffer[2048]; + GetModuleFileNameW(NULL, buffer, 2048); + runtime = _wfopen(buffer, L"rb"); +#else + runtime = fopen(argv[0], "rb"); +#endif // Get offsets fseek(runtime, -12, SEEK_END); @@ -199,3 +218,13 @@ main(int argc, char *argv[]) { PyImport_FrozenModules = _PyImport_FrozenModules; return Py_FrozenMain(argc, argv); } + +#ifdef WIN_UNICODE +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *lpCmdLine, int nCmdShow) { + return wmain(__argc, __wargv); +} +#elif defined(_WIN32) +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char *lpCmdLine, int nCmdShow) { + return main(__argc, __argv); +} +#endif From fcbc71c91b89ad3169dd6a4b6a8d4f62557554b7 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 5 Jan 2017 18:38:35 -0800 Subject: [PATCH 068/424] deploy-ng: Stop using freezer outside the scope of its for loop --- direct/src/showutil/dist.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 12bda7823a..9ec399f805 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -97,6 +97,8 @@ class build(distutils.command.build.build): # Create runtime + freezer_extras = set() + freezer_modules = set() for app in self.distribution.applications: freezer = FreezeTool.Freezer() freezer.addModule('__main__', filename=app.scriptname) @@ -106,8 +108,11 @@ class build(distutils.command.build.build): freezer.generateRuntimeFromStub(os.path.join(builddir, app.runtimename), stub_file) stub_file.close() + freezer_extras.update(freezer.extras) + freezer_modules.update(freezer.getAllModuleNames()) + # Copy extension modules - for module, source_path in freezer.extras: + for module, source_path in freezer_extras: if source_path is None: # Built-in module. continue @@ -165,7 +170,7 @@ class build(distutils.command.build.build): # Copy Game Files ignore_copy_list = [ '__pycache__', - ] + freezer.getAllModuleNames() + self.distribution.exclude_paths + [i.scriptname for i in self.distribution.applications] + ] + list(freezer_modules) + self.distribution.exclude_paths + [i.scriptname for i in self.distribution.applications] for copydir in self.distribution.directories: for item in os.listdir(copydir): From 41556b07fc872216c645d8f882547ae12e021e86 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 5 Jan 2017 18:39:11 -0800 Subject: [PATCH 069/424] deploy-ng: Use a more generic method to remove version strings from extension module names This should help with stripping these strings on Windows. --- direct/src/showutil/dist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 9ec399f805..88af7c2a54 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -123,7 +123,10 @@ class build(distutils.command.build.build): basename = module.rsplit('.', 1)[0] + '.' + basename # Remove python version string - basename = '.'.join([i for i in basename.split('.') if not i.startswith('cpython-')]) + if sys.version_info >= (3, 0): + parts = basename.split('.') + parts = parts[:-2] + parts[-1:] + basename = '.'.join(parts) target_path = os.path.join(builddir, basename) if '.whl/' in source_path: From c746296e57169bb464be3e25b60cf2f71e5c314e Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 5 Jan 2017 19:09:22 -0800 Subject: [PATCH 070/424] makepanda: Give WHLVERSION a better default than None --- makepanda/makepanda.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 2ec86ef499..5a9c2d6656 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -368,6 +368,9 @@ if VERSION is None: else: VERSION = ParsePandaVersion("dtool/PandaVersion.pp") +if WHLVERSION is None: + WHLVERSION = ParsePandaVersion("dtool/PandaVersion.pp") + print("Version: %s" % VERSION) if RUNTIME or RTDIST: print("Core API Version: %s" % COREAPI_VERSION) From 465d8bf1bd1fc824e54fff6b8a38b0b745ea39df Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 5 Jan 2017 19:22:57 -0800 Subject: [PATCH 071/424] makewheel: Copy Python stdlib C extension modules to deploy_libs --- makepanda/makewheel.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 56fb43133e..8f04e2cb81 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -80,6 +80,11 @@ def is_fat_file(path): open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA') +def get_python_ext_module_dir(): + import _sha1 + return os.path.dirname(_sha1.__file__) + + if sys.platform in ('win32', 'cygwin'): is_executable = is_exe_file elif sys.platform == 'darwin': @@ -506,19 +511,24 @@ def makewheel(version, output_dir, platform=default_platform): ext_suffix = GetExtensionSuffix() - for file in os.listdir(panda3d_dir): - if file == '__init__.py': - pass - elif file.endswith(ext_suffix) or file.endswith('.py'): - source_path = os.path.join(panda3d_dir, file) + ext_mod_dirs = [ + (panda3d_dir, 'panda3d/'), + (get_python_ext_module_dir(), 'deploy_libs/'), + ] + for srcdir, targetdir in ext_mod_dirs: + for file in os.listdir(srcdir): + if file == '__init__.py': + pass + elif file.endswith(ext_suffix) or file.endswith('.py'): + source_path = os.path.join(srcdir, file) - if file.endswith('.pyd') and platform.startswith('cygwin'): - # Rename it to .dll for cygwin Python to be able to load it. - target_path = 'panda3d/' + os.path.splitext(file)[0] + '.dll' - else: - target_path = 'panda3d/' + file + if file.endswith('.pyd') and platform.startswith('cygwin'): + # Rename it to .dll for cygwin Python to be able to load it. + target_path = targetdir + os.path.splitext(file)[0] + '.dll' + else: + target_path = targetdir + file - whl.write_file(target_path, source_path) + whl.write_file(target_path, source_path) # Add plug-ins. for lib in PLUGIN_LIBS: From 6199d84131c4c9ba5ece5c3ca52c4c06fe54a8c6 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 5 Jan 2017 19:23:47 -0800 Subject: [PATCH 072/424] deploy-ng: Grab Python stdlib C extension modules from panda3d whl --- direct/src/showutil/dist.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 88af7c2a54..9cf30f5693 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -40,6 +40,11 @@ def find_packages(whlfile): return [i for i in filelist if '.so.' in i or i.endswith('.dll') or i.endswith('.dylib') or 'libpandagl' in i] +def get_python_ext_module_dir(): + import _sha1 + return os.path.dirname(_sha1.__file__) + + class build(distutils.command.build.build): def run(self): distutils.command.build.build.run(self) @@ -128,6 +133,10 @@ class build(distutils.command.build.build): parts = parts[:-2] + parts[-1:] basename = '.'.join(parts) + if use_wheels and source_path.startswith(get_python_ext_module_dir()): + # Look in panda3d whl instead + source_path = source_path.replace(get_python_ext_module_dir(), '{}/deploy_libs'.format(p3dwhlfn)) + target_path = os.path.join(builddir, basename) if '.whl/' in source_path: # This was found in a wheel, extract it From de598d966b662f71e317cb0be01966b32f9e844c Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 5 Jan 2017 19:24:51 -0800 Subject: [PATCH 073/424] asteroid sample: Add requirements.txt and wheels to setup.py exclude_paths --- samples/asteroids/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index a0e7c0474f..7163b7900f 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -3,7 +3,7 @@ from direct.showutil.dist import * setup( name="asteroids", directories=['.'], - exclude_paths=['build', 'setup.py'], + exclude_paths=['build', 'setup.py', 'requirements.txt', 'wheels'], applications=[Application('main.py', 'asteroids')], deploy_platforms=['linux_x86_64'], ) From 2fa4ef1c7ececcb4f0c0e0f37e01523672266cc0 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 5 Jan 2017 20:17:36 -0800 Subject: [PATCH 074/424] makewheel/deploy-ng: Try import cmath instead of _sha1 for get_python_ext_module_dir() The Python2 Windows buildbot did not like _sha1. --- direct/src/showutil/dist.py | 4 ++-- makepanda/makewheel.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 9cf30f5693..b0933db143 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -41,8 +41,8 @@ def find_packages(whlfile): def get_python_ext_module_dir(): - import _sha1 - return os.path.dirname(_sha1.__file__) + import cmath + return os.path.dirname(cmath.__file__) class build(distutils.command.build.build): diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 8f04e2cb81..d1e67b185e 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -81,8 +81,8 @@ def is_fat_file(path): def get_python_ext_module_dir(): - import _sha1 - return os.path.dirname(_sha1.__file__) + import cmath + return os.path.dirname(cmath.__file__) if sys.platform in ('win32', 'cygwin'): From 061a2745f94d732ef700affc17589fd878bf5c39 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 5 Jan 2017 20:24:56 -0800 Subject: [PATCH 075/424] deploy-ng: Add use_console option to Application This option only affects deploying to Windows. If set to False (the default), deploy-stubw is used. If True, deploy-stub is used. NOTE: This is mostly untested. The only check was that it didn't break when deploying to Linux. --- direct/src/showutil/dist.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index b0933db143..fad55b5880 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -14,7 +14,11 @@ from direct.showutil import FreezeTool import panda3d.core as p3d -Application = collections.namedtuple('Application', 'scriptname runtimename') +class Application(object): + def __init__(self, scriptname, runtimename, use_console=False): + self.scriptname = scriptname + self.runtimename = runtimename + self.use_console = use_console class Distribution(distutils.dist.Distribution): @@ -85,21 +89,10 @@ class build(distutils.command.build.build): raise RuntimeError("Missing panda3d wheel") whlfiles = {whl: zipfile.ZipFile(whl) for whl in wheelpaths} - stub_path = 'panda3d_tools/deploy-stub' - if platform.startswith('win'): - stub_path += '.exe' - stub_file = p3dwhl.open(stub_path) # Add whl files to the path so they are picked up by modulefinder for whl in wheelpaths: sys.path.insert(0, whl) - else: - dtool_path = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()).to_os_specific() - stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', 'deploy-stub') - if platform.startswith('win'): - stub_path += '.exe' - stub_file = open(stub_path, 'rb') - # Create runtime freezer_extras = set() @@ -110,6 +103,20 @@ class build(distutils.command.build.build): for exmod in self.distribution.exclude_modules: freezer.excludeModule(exmod) freezer.done(addStartupModules=True) + + stub_name = 'deploy-stub' + if platform.startswith('win'): + if not app.use_console: + stub_name = 'deploy-stubw' + stub_name += '.exe' + + if use_wheels: + stub_file = p3dwhl.open('panda3d_tools/{}'.format(stub_name)) + else: + dtool_path = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()).to_os_specific() + stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name) + stub_file = open(stub_path, 'rb') + freezer.generateRuntimeFromStub(os.path.join(builddir, app.runtimename), stub_file) stub_file.close() From a5bea8f8b081cbbcf60a5c0b5d8705771a2f1dd9 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 5 Jan 2017 20:35:59 -0800 Subject: [PATCH 076/424] makewheel/deploy-ng: Use inspect.getfile(cmath) instead of cmath.__file__ for get_python_ext_module_dir() --- direct/src/showutil/dist.py | 3 ++- makepanda/makewheel.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index fad55b5880..a8b3bc446c 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -46,7 +46,8 @@ def find_packages(whlfile): def get_python_ext_module_dir(): import cmath - return os.path.dirname(cmath.__file__) + import inspect + return os.path.dirname(inspect.getfile(cmath)) class build(distutils.command.build.build): diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index d1e67b185e..42847b2102 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -82,7 +82,8 @@ def is_fat_file(path): def get_python_ext_module_dir(): import cmath - return os.path.dirname(cmath.__file__) + import inspect + return os.path.dirname(inspect.getfile(cmath)) if sys.platform in ('win32', 'cygwin'): From 40c23e5cf18de207a8226f5990e3bbd1327a96d4 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Fri, 6 Jan 2017 08:07:20 -0800 Subject: [PATCH 077/424] makewheel: Use _ctypes instead of cmath for get_python_ext_module_dir() Also switch back to using __file__ instead of inspect. --- makepanda/makewheel.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 42847b2102..af4dd30c90 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -81,9 +81,8 @@ def is_fat_file(path): def get_python_ext_module_dir(): - import cmath - import inspect - return os.path.dirname(inspect.getfile(cmath)) + import _ctypes + return os.path.dirname(_ctypes.__file__) if sys.platform in ('win32', 'cygwin'): From fff8c82d7c90623f457a2dba20a190a6c726fb30 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 7 Jan 2017 10:19:04 -0800 Subject: [PATCH 078/424] deploy-ng: Correctly add deploy_libs to sys.path Now modulefinder will pick up on the Python stdlib C extension modules in the panda3d whl, and no special path hacks are required. --- direct/src/showutil/dist.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index a8b3bc446c..876de9d23d 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -44,12 +44,6 @@ def find_packages(whlfile): return [i for i in filelist if '.so.' in i or i.endswith('.dll') or i.endswith('.dylib') or 'libpandagl' in i] -def get_python_ext_module_dir(): - import cmath - import inspect - return os.path.dirname(inspect.getfile(cmath)) - - class build(distutils.command.build.build): def run(self): distutils.command.build.build.run(self) @@ -95,6 +89,10 @@ class build(distutils.command.build.build): for whl in wheelpaths: sys.path.insert(0, whl) + # Add deploy_libs from panda3d whl to the path + sys.path.insert(0, os.path.join(p3dwhlfn, 'deploy_libs')) + + # Create runtime freezer_extras = set() freezer_modules = set() @@ -141,10 +139,6 @@ class build(distutils.command.build.build): parts = parts[:-2] + parts[-1:] basename = '.'.join(parts) - if use_wheels and source_path.startswith(get_python_ext_module_dir()): - # Look in panda3d whl instead - source_path = source_path.replace(get_python_ext_module_dir(), '{}/deploy_libs'.format(p3dwhlfn)) - target_path = os.path.join(builddir, basename) if '.whl/' in source_path: # This was found in a wheel, extract it From a03448509e498a8520d31f6b32ea78d32aa21af4 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 7 Jan 2017 10:21:31 -0800 Subject: [PATCH 079/424] deploy-ng: Use a clean sys.path for each platform --- direct/src/showutil/dist.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 876de9d23d..2f57e32a2f 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -55,6 +55,8 @@ class build(distutils.command.build.build): use_wheels = True print("Building platforms: {}".format(','.join(platforms))) + saved_path = sys.path[:] + for platform in platforms: builddir = os.path.join(self.build_base, platform) @@ -86,6 +88,7 @@ class build(distutils.command.build.build): whlfiles = {whl: zipfile.ZipFile(whl) for whl in wheelpaths} # Add whl files to the path so they are picked up by modulefinder + sys.path = saved_path[:] for whl in wheelpaths: sys.path.insert(0, whl) From 25b167c0805d18a3808b25b88b562f95c0708c1a Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 9 Jan 2017 19:49:18 -0800 Subject: [PATCH 080/424] makewheel: Use less restrictive extension module suffix on py3 --- makepanda/makewheel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index af4dd30c90..a110386438 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -509,7 +509,7 @@ def makewheel(version, output_dir, platform=default_platform): # default one adds the bin directory to the PATH, which we don't have. whl.write_file_data('panda3d/__init__.py', '') - ext_suffix = GetExtensionSuffix() + ext_suffix = '.pyd' if sys.platform in ('win32', 'cygwin') else '.so' ext_mod_dirs = [ (panda3d_dir, 'panda3d/'), From db4516b796a4fcff65e5a17e747fc3a75170ec32 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 9 Jan 2017 20:14:25 -0800 Subject: [PATCH 081/424] deploy-ng: Look for correct platform suffixes for Python extension modules when cross-building Note: We still need to supply suffixes for macOS. Also, Linux suffixes are based on suffixes on my system (Arch, Python3) and will likely need to be tweaked. --- direct/src/showutil/FreezeTool.py | 24 +++++++++++++++++++++++- direct/src/showutil/dist.py | 7 +++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 1c07fdbda4..0b212afba0 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -717,6 +717,28 @@ class Freezer: if path: modulefinder.AddPackagePath(moduleName, path[0]) + # Suffix/extension for Python C extension modules + if self.platform == PandaSystem.getPlatform(): + self.moduleSuffixes = imp.get_suffixes() + else: + self.moduleSuffixes = [('.py', 'r', 1), ('.pyc', 'rb', 2)] + if 'linux' in self.platform: + self.moduleSuffixes += [ + ('.cpython-{0}{1}m-x86_64-linux-gnu.so'.format(*sys.version_info), 'rb', 3), + ('.cpython-{0}{1}m-i686-linux-gnu.so'.format(*sys.version_info), 'rb', 3), + ('.abi{}.so'.format(sys.version_info[0]), 'rb', 3), + ('.so', 'rb', 3), + ] + elif 'win' in self.platform: + self.moduleSuffixes += [ + ('.cp{0}{1}-win_amd64.pyd'.format(*sys.version_info), 'rb', 3), + ('.cp{0}{1}-win32.pyd'.format(*sys.version_info), 'rb', 3), + ('.dll', 'rb', 3), + ] + else: + print("Unknown platform %s" % (self.platform)) + self.moduleSuffixes = imp.get_suffixes() + def excludeFrom(self, freezer): """ Excludes all modules that have already been processed by the indicated FreezeTool. This is equivalent to passing the @@ -966,7 +988,7 @@ class Freezer: else: includes.append(mdef) - self.mf = PandaModuleFinder(excludes = list(excludeDict.keys())) + self.mf = PandaModuleFinder(excludes = list(excludeDict.keys()), suffixes=self.moduleSuffixes) # Attempt to import the explicit modules into the modulefinder. diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2f57e32a2f..b511b77f5f 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -100,7 +100,7 @@ class build(distutils.command.build.build): freezer_extras = set() freezer_modules = set() for app in self.distribution.applications: - freezer = FreezeTool.Freezer() + freezer = FreezeTool.Freezer(platform=platform) freezer.addModule('__main__', filename=app.scriptname) for exmod in self.distribution.exclude_modules: freezer.excludeModule(exmod) @@ -226,7 +226,10 @@ class bdist_panda3d(distutils.core.Command): pass def run(self): - platforms = [p3d.PandaSystem.get_platform()] + if not self.distribution.deploy_platforms: + platforms = [p3d.PandaSystem.get_platform()] + else: + platforms = self.distribution.deploy_platforms build_base = os.path.join(os.getcwd(), 'build') self.run_command("build") From 8352f27a23a3a3694f1afd7107d1942595d37c26 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 10 Jan 2017 23:15:30 +0100 Subject: [PATCH 082/424] makepanda: deploy-stub needs to link to libpython --- makepanda/makepanda.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 59df37c4e6..b24430db6f 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -837,9 +837,6 @@ if (COMPILER=="GCC"): if not PkgSkip("PYTHON"): python_lib = SDK["PYTHONVERSION"] - if not RTDIST: - # We don't link anything in the SDK with libpython. - python_lib = "" SmartPkgEnable("PYTHON", "", python_lib, (SDK["PYTHONVERSION"], SDK["PYTHONVERSION"] + "/Python.h")) SmartPkgEnable("OPENSSL", "openssl", ("ssl", "crypto"), ("openssl/ssl.h", "openssl/crypto.h")) From 3347a6e2cb6fef1108d38e3754ee8d293b754a55 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 10 Jan 2017 18:42:59 -0800 Subject: [PATCH 083/424] makewheel: Add libpython to deploy_libs TODO: See if there is a better way to do this and to test/implement macOS support. --- makepanda/makewheel.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index a110386438..cc69624dc1 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -590,6 +590,15 @@ def makewheel(version, output_dir, platform=default_platform): whl.write_file(info_dir + '/README.md', readme_src) whl.write_file_data(info_dir + '/top_level.txt', 'direct\npanda3d\npandac\npanda3d_tools\n') + # Add libpython for deployment + if sys.platform in ('win32', 'cygwin'): + pylib_name = 'python{0}{1}.dll'.format(*sys.version_info) + pylib_path = os.path.join(get_config_var('BINDIR'), pylib_name) + else: + pylib_name = get_config_var('LDLIBRARY') + pylib_path = os.path.join(get_config_var('LIBDIR'), pylib_name) + whl.write_file('/deploy_libs/' + pylib_name, pylib_path) + whl.close() From 83c6f033e15dcb67c769ca309a799877a0d12ba5 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 10 Jan 2017 18:44:13 -0800 Subject: [PATCH 084/424] deploy-ng: Add libpython to list of libs to copy --- direct/src/showutil/dist.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index b511b77f5f..2d4f3e8ea7 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -41,7 +41,14 @@ def find_packages(whlfile): else: filelist = whlfile.namelist() - return [i for i in filelist if '.so.' in i or i.endswith('.dll') or i.endswith('.dylib') or 'libpandagl' in i] + return [ + i for i in filelist + if '.so.' in i or + i.endswith('.dll') or + i.endswith('.dylib') or + 'libpandagl' in i or + 'libpython' in i + ] class build(distutils.command.build.build): From c1d273eb4051443b1816325f33e80beae7466b8b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 10 Jan 2017 18:51:50 -0800 Subject: [PATCH 085/424] deploy-ng: Make sure cp437 encoding is available when deploying to Windows --- direct/src/showutil/dist.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2d4f3e8ea7..cb0cb7c4dc 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -109,6 +109,8 @@ class build(distutils.command.build.build): for app in self.distribution.applications: freezer = FreezeTool.Freezer(platform=platform) freezer.addModule('__main__', filename=app.scriptname) + if platform.startswith('win'): + freezer.addModule('encodings.cp437') for exmod in self.distribution.exclude_modules: freezer.excludeModule(exmod) freezer.done(addStartupModules=True) From 3fabe6a8ded5966e229632764446d4aee82f840b Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 14 Jan 2017 15:35:08 +0100 Subject: [PATCH 086/424] makewheel: fix inclusion of libpython on macOS --- makepanda/makewheel.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index cc69624dc1..e485413926 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -594,6 +594,9 @@ def makewheel(version, output_dir, platform=default_platform): if sys.platform in ('win32', 'cygwin'): pylib_name = 'python{0}{1}.dll'.format(*sys.version_info) pylib_path = os.path.join(get_config_var('BINDIR'), pylib_name) + elif sys.platform == 'darwin': + pylib_name = 'libpython{0}.{1}.dylib'.format(*sys.version_info) + pylib_path = os.path.join(get_config_var('LIBDIR'), pylib_name) else: pylib_name = get_config_var('LDLIBRARY') pylib_path = os.path.join(get_config_var('LIBDIR'), pylib_name) From af57eac463aedf3815e6db1cd452960263c1ce00 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 14 Jan 2017 11:53:29 -0800 Subject: [PATCH 087/424] deploy-ng: Add moduleSuffixes for macOS --- direct/src/showutil/FreezeTool.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 0b212afba0..e08393e978 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -735,6 +735,10 @@ class Freezer: ('.cp{0}{1}-win32.pyd'.format(*sys.version_info), 'rb', 3), ('.dll', 'rb', 3), ] + elif 'mac' in self.platform: + self.moduleSuffixes += [ + ('.cpython-{0}{1}m-darwin.so'.format(*sys.version_info), 'rb', 3), + ] else: print("Unknown platform %s" % (self.platform)) self.moduleSuffixes = imp.get_suffixes() From 04e4817667f737846c61bde5b98b4cdef5535428 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 19 Jan 2017 19:03:59 -0800 Subject: [PATCH 088/424] deploy-ng: Add option to specify a different requirements.txt file for pip --- direct/src/showutil/dist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index cb0cb7c4dc..bd62ffbe2e 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -29,6 +29,7 @@ class Distribution(distutils.dist.Distribution): self.exclude_paths = [] self.exclude_modules = [] self.deploy_platforms = [] + self.requirements_path = './requirements.txt' distutils.dist.Distribution.__init__(self, attrs) @@ -76,7 +77,7 @@ class build(distutils.command.build.build): pip.main(args=[ 'download', '-d', whldir, - '-r', 'requirements.txt', + '-r', self.distribution.requirements_path, '--only-binary', ':all:', '--platform', platform, ]) From c789a3cae803177d881c168da3826646ce5b67f2 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 19 Jan 2017 19:08:28 -0800 Subject: [PATCH 089/424] deploy-ng: Add option for extra PyPI indexes --- direct/src/showutil/dist.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index bd62ffbe2e..efded4fa91 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -30,6 +30,7 @@ class Distribution(distutils.dist.Distribution): self.exclude_modules = [] self.deploy_platforms = [] self.requirements_path = './requirements.txt' + self.pypi_extra_indexes = [] distutils.dist.Distribution.__init__(self, attrs) @@ -74,13 +75,18 @@ class build(distutils.command.build.build): if use_wheels: whldir = os.path.join(self.build_base, '__whl_cache__') - pip.main(args=[ + pip_args = [ 'download', '-d', whldir, '-r', self.distribution.requirements_path, '--only-binary', ':all:', '--platform', platform, - ]) + ] + + for index in self.distribution.pypi_extra_indexes: + pip_args += ['--extra-index-url', index] + + pip.main(args=pip_args) wheelpaths = [os.path.join(whldir,i) for i in os.listdir(whldir) if platform in i] From eec5c997c0237ed5329421d5b052f605bae684ab Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 19 Jan 2017 19:43:11 -0800 Subject: [PATCH 090/424] deploy-ng: Use length-prefixed strings for writing modules to deploy-stub This wastes less space than always using 256 characters for every module name. --- direct/src/showutil/FreezeTool.py | 20 ++++++++++++++++++-- pandatool/src/deploy-stub/deploy-stub.c | 4 +++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index e08393e978..b5190b6fe9 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1579,10 +1579,26 @@ class Freezer: if getattr(module, "__path__", None): # Indicate package by negative size size = -size - return struct.pack('<256sIi', modulename.encode('ascii'), offset, size) + modname = modulename.encode('ascii') + modnamelen = len(modulename) + return struct.pack( + ' Date: Mon, 23 Jan 2017 21:22:10 -0800 Subject: [PATCH 091/424] deploy-ng: Run egg2bam on egg files Also adds support for running scripts based on file extensions. --- direct/src/showutil/dist.py | 44 +++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index efded4fa91..4a3d254f14 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -2,6 +2,7 @@ import collections import os import pip import sys +import subprocess import zipfile import distutils.command.build @@ -31,6 +32,9 @@ class Distribution(distutils.dist.Distribution): self.deploy_platforms = [] self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] + self.build_scripts= { + '.egg': ('.bam', 'egg2bam -o {1} {0}'), + } distutils.dist.Distribution.__init__(self, attrs) @@ -206,20 +210,36 @@ class build(distutils.command.build.build): ] + list(freezer_modules) + self.distribution.exclude_paths + [i.scriptname for i in self.distribution.applications] for copydir in self.distribution.directories: - for item in os.listdir(copydir): - src = os.path.join(copydir, item) - dst = os.path.join(builddir, item) + for root, dirs, files in os.walk(copydir): + for item in files: + src = os.path.join(root, item) + dst = os.path.normpath(os.path.join(builddir, root, item)) - if item in ignore_copy_list: - print("skipping", src) - continue + if item in ignore_copy_list: + print("skipping", src) + continue - if os.path.isdir(src): - #print("Copy dir", src, dst) - distutils.dir_util.copy_tree(src, dst) - else: - #print("Copy file", src, dst) - distutils.file_util.copy_file(src, dst) + ext = os.path.splitext(src)[1] + dst_root = os.path.splitext(dst)[0] + + if ext in self.distribution.build_scripts: + dst_ext, script = self.distribution.build_scripts[ext] + dst = dst_root + dst_ext + script = script.format(src, dst) + print("using script:", script) + subprocess.call(script.split()) + else: + #print("Copy file", src, dst) + distutils.file_util.copy_file(src, dst) + + for item in dirs[:]: + path = os.path.join(builddir, root, item) + if item in ignore_copy_list: + print("skipping", path) + dirs.remove(item) + else: + print("making directory", path) + distutils.dir_util.mkpath(path) # Copy extra files for extra in self.distribution.files: From 3282656f0ec2a3b9a5c5f3d39feb9d7f5cebd74f Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 25 Jan 2017 13:34:06 +0100 Subject: [PATCH 092/424] makepanda: linking deploy-stub on Linux requires -lrt and -lutil --- makepanda/makepanda.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index b24430db6f..7d6d1fae69 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -839,6 +839,10 @@ if (COMPILER=="GCC"): python_lib = SDK["PYTHONVERSION"] SmartPkgEnable("PYTHON", "", python_lib, (SDK["PYTHONVERSION"], SDK["PYTHONVERSION"] + "/Python.h")) + if GetTarget() == "linux": + LibName("PYTHON", "-lutil") + LibName("PYTHON", "-lrt") + SmartPkgEnable("OPENSSL", "openssl", ("ssl", "crypto"), ("openssl/ssl.h", "openssl/crypto.h")) SmartPkgEnable("ZLIB", "zlib", ("z"), "zlib.h") SmartPkgEnable("GTK2", "gtk+-2.0") From 5cfbd57d994efe2cc25e903b64a1c6cac3887430 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 28 Jan 2017 19:36:42 -0800 Subject: [PATCH 093/424] FreezeTool: Add encodings.cp437 and imp to startupModules --- direct/src/showutil/FreezeTool.py | 2 +- direct/src/showutil/dist.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index b5190b6fe9..932890d7c2 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -32,7 +32,7 @@ isDebugBuild = (python.lower().endswith('_d')) # must be frozen in any main.exe. startupModules = [ 'encodings.cp1252', 'encodings.latin_1', 'encodings.utf_8', - 'encodings.mbcs', 'encodings.cp850', + 'encodings.mbcs', 'encodings.cp850', 'encodings.cp437', 'imp', ] if sys.version_info >= (3, 0): startupModules += ['io', 'marshal', 'importlib.machinery', 'importlib.util'] diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 4a3d254f14..da5ccc8d33 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -120,8 +120,6 @@ class build(distutils.command.build.build): for app in self.distribution.applications: freezer = FreezeTool.Freezer(platform=platform) freezer.addModule('__main__', filename=app.scriptname) - if platform.startswith('win'): - freezer.addModule('encodings.cp437') for exmod in self.distribution.exclude_modules: freezer.excludeModule(exmod) freezer.done(addStartupModules=True) From d349501265db86e8cad876ea8cb2ab1a7a268452 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 28 Jan 2017 19:37:06 -0800 Subject: [PATCH 094/424] deploy-ng: Rename bdist_panda3d to bdist_p3d_archive This leaves room for a later bdist_p3d_installer option. --- direct/src/showutil/dist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index da5ccc8d33..02372b48f3 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -250,7 +250,7 @@ class build(distutils.command.build.build): distutils.file_util.copy_file(src, dst) -class bdist_panda3d(distutils.core.Command): +class bdist_p3d_archive(distutils.core.Command): user_options = [] def initialize_options(self): @@ -288,5 +288,5 @@ def setup(**attrs): attrs.setdefault("distclass", Distribution) commandClasses = attrs.setdefault("cmdclass", {}) commandClasses['build'] = build - commandClasses['bdist_panda3d'] = bdist_panda3d + commandClasses['bdist_p3d_archive'] = bdist_p3d_archive distutils.core.setup(**attrs) From 159a1aa9481a680538c533452074af4582ffb9fd Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 28 Jan 2017 19:59:35 -0800 Subject: [PATCH 095/424] deploy-ng: Rename dist.build to dist.build_p3d and stop subclassing the builtin build Command This also removes the need for a custom Distribution class. --- direct/src/showutil/dist.py | 74 ++++++++++++++++++------------------- samples/asteroids/setup.py | 17 +++++++-- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 02372b48f3..389236873b 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -5,10 +5,8 @@ import sys import subprocess import zipfile -import distutils.command.build import distutils.core import distutils.dir_util -import distutils.dist import distutils.file_util from direct.showutil import FreezeTool @@ -22,22 +20,6 @@ class Application(object): self.use_console = use_console -class Distribution(distutils.dist.Distribution): - def __init__(self, attrs): - self.applications = [] - self.directories = [] - self.files = [] - self.exclude_paths = [] - self.exclude_modules = [] - self.deploy_platforms = [] - self.requirements_path = './requirements.txt' - self.pypi_extra_indexes = [] - self.build_scripts= { - '.egg': ('.bam', 'egg2bam -o {1} {0}'), - } - distutils.dist.Distribution.__init__(self, attrs) - - # TODO replace with Packager def find_packages(whlfile): if whlfile is None: @@ -57,14 +39,32 @@ def find_packages(whlfile): ] -class build(distutils.command.build.build): +class build_p3d(distutils.core.Command): + user_options = [] # TODO + + def initialize_options(self): + self.build_base = os.path.join(os.getcwd(), 'build') + self.applications = [] + self.directories = [] + self.files = [] + self.exclude_paths = [] + self.exclude_modules = [] + self.deploy_platforms = [] + self.requirements_path = './requirements.txt' + self.pypi_extra_indexes = [] + self.build_scripts= { + '.egg': ('.bam', 'egg2bam -o {1} {0}'), + } + + def finalize_options(self): + pass + def run(self): - distutils.command.build.build.run(self) - if not self.distribution.deploy_platforms: + if not self.deploy_platforms: platforms = [p3d.PandaSystem.get_platform()] use_wheels = False else: - platforms = self.distribution.deploy_platforms + platforms = self.deploy_platforms use_wheels = True print("Building platforms: {}".format(','.join(platforms))) @@ -82,12 +82,12 @@ class build(distutils.command.build.build): pip_args = [ 'download', '-d', whldir, - '-r', self.distribution.requirements_path, + '-r', self.requirements_path, '--only-binary', ':all:', '--platform', platform, ] - for index in self.distribution.pypi_extra_indexes: + for index in self.pypi_extra_indexes: pip_args += ['--extra-index-url', index] pip.main(args=pip_args) @@ -117,10 +117,10 @@ class build(distutils.command.build.build): # Create runtime freezer_extras = set() freezer_modules = set() - for app in self.distribution.applications: + for app in self.applications: freezer = FreezeTool.Freezer(platform=platform) freezer.addModule('__main__', filename=app.scriptname) - for exmod in self.distribution.exclude_modules: + for exmod in self.exclude_modules: freezer.excludeModule(exmod) freezer.done(addStartupModules=True) @@ -205,9 +205,9 @@ class build(distutils.command.build.build): # Copy Game Files ignore_copy_list = [ '__pycache__', - ] + list(freezer_modules) + self.distribution.exclude_paths + [i.scriptname for i in self.distribution.applications] + ] + list(freezer_modules) + self.exclude_paths + [i.scriptname for i in self.applications] - for copydir in self.distribution.directories: + for copydir in self.directories: for root, dirs, files in os.walk(copydir): for item in files: src = os.path.join(root, item) @@ -220,8 +220,8 @@ class build(distutils.command.build.build): ext = os.path.splitext(src)[1] dst_root = os.path.splitext(dst)[0] - if ext in self.distribution.build_scripts: - dst_ext, script = self.distribution.build_scripts[ext] + if ext in self.build_scripts: + dst_ext, script = self.build_scripts[ext] dst = dst_root + dst_ext script = script.format(src, dst) print("using script:", script) @@ -240,7 +240,7 @@ class build(distutils.command.build.build): distutils.dir_util.mkpath(path) # Copy extra files - for extra in self.distribution.files: + for extra in self.files: if len(extra) == 2: src, dst = extra dst = os.path.join(builddir, dst) @@ -260,13 +260,14 @@ class bdist_p3d_archive(distutils.core.Command): pass def run(self): - if not self.distribution.deploy_platforms: + build_cmd = self.get_finalized_command('build_p3d') + if not build_cmd.deploy_platforms: platforms = [p3d.PandaSystem.get_platform()] else: - platforms = self.distribution.deploy_platforms - build_base = os.path.join(os.getcwd(), 'build') + platforms = build_cmd.deploy_platforms + build_base = build_cmd.build_base - self.run_command("build") + self.run_command('build_p3d') os.chdir(build_base) for platform in platforms: @@ -285,8 +286,7 @@ class bdist_p3d_archive(distutils.core.Command): distutils.dir_util.remove_tree(temp_dir) def setup(**attrs): - attrs.setdefault("distclass", Distribution) commandClasses = attrs.setdefault("cmdclass", {}) - commandClasses['build'] = build + commandClasses['build_p3d'] = build_p3d commandClasses['bdist_p3d_archive'] = bdist_p3d_archive distutils.core.setup(**attrs) diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 7163b7900f..56e859e307 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -2,8 +2,17 @@ from direct.showutil.dist import * setup( name="asteroids", - directories=['.'], - exclude_paths=['build', 'setup.py', 'requirements.txt', 'wheels'], - applications=[Application('main.py', 'asteroids')], - deploy_platforms=['linux_x86_64'], + options = { + 'build_p3d': { + 'directories': ['.'], + 'exclude_paths': ['build', 'setup.py', 'requirements.txt', 'wheels'], + 'applications': [Application('main.py', 'asteroids')], + 'deploy_platforms': [ + 'manylinux1_x86_64', + 'macosx_10_6_x86_64', + 'win32', + 'win_amd64', + ], + } + } ) From 9723cb62d9108b16192a520e38cfcb05c9822506 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 28 Jan 2017 20:17:04 -0800 Subject: [PATCH 096/424] deploy-ng: Split applications list into gui_apps and console_apps dictionaries The keys to these new dictionaries are the name of the application to create and the value is the script that is the entry point for the application. This gets rid of the need for an Application class. --- direct/src/showutil/dist.py | 28 +++++++++++++++------------- samples/asteroids/setup.py | 4 +++- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 389236873b..5a0f10c109 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -13,12 +13,6 @@ from direct.showutil import FreezeTool import panda3d.core as p3d -class Application(object): - def __init__(self, scriptname, runtimename, use_console=False): - self.scriptname = scriptname - self.runtimename = runtimename - self.use_console = use_console - # TODO replace with Packager def find_packages(whlfile): @@ -44,7 +38,8 @@ class build_p3d(distutils.core.Command): def initialize_options(self): self.build_base = os.path.join(os.getcwd(), 'build') - self.applications = [] + self.gui_apps = {} + self.console_apps = {} self.directories = [] self.files = [] self.exclude_paths = [] @@ -57,6 +52,7 @@ class build_p3d(distutils.core.Command): } def finalize_options(self): + # TODO pass def run(self): @@ -114,19 +110,19 @@ class build_p3d(distutils.core.Command): sys.path.insert(0, os.path.join(p3dwhlfn, 'deploy_libs')) - # Create runtime + # Create runtimes freezer_extras = set() freezer_modules = set() - for app in self.applications: + def create_runtime(appname, mainscript, use_console): freezer = FreezeTool.Freezer(platform=platform) - freezer.addModule('__main__', filename=app.scriptname) + freezer.addModule('__main__', filename=mainscript) for exmod in self.exclude_modules: freezer.excludeModule(exmod) freezer.done(addStartupModules=True) stub_name = 'deploy-stub' if platform.startswith('win'): - if not app.use_console: + if use_console: stub_name = 'deploy-stubw' stub_name += '.exe' @@ -137,12 +133,18 @@ class build_p3d(distutils.core.Command): stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name) stub_file = open(stub_path, 'rb') - freezer.generateRuntimeFromStub(os.path.join(builddir, app.runtimename), stub_file) + freezer.generateRuntimeFromStub(os.path.join(builddir, appname), stub_file) stub_file.close() freezer_extras.update(freezer.extras) freezer_modules.update(freezer.getAllModuleNames()) + for appname, scriptname in self.gui_apps.items(): + create_runtime(appname, scriptname, False) + + for appname, scriptname in self.console_apps.items(): + create_runtime(appname, scriptname, True) + # Copy extension modules for module, source_path in freezer_extras: if source_path is None: @@ -205,7 +207,7 @@ class build_p3d(distutils.core.Command): # Copy Game Files ignore_copy_list = [ '__pycache__', - ] + list(freezer_modules) + self.exclude_paths + [i.scriptname for i in self.applications] + ] + list(freezer_modules) + self.exclude_paths + list(self.gui_apps.values()) + list(self.console_apps.values()) for copydir in self.directories: for root, dirs, files in os.walk(copydir): diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 56e859e307..16d433b149 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -6,7 +6,9 @@ setup( 'build_p3d': { 'directories': ['.'], 'exclude_paths': ['build', 'setup.py', 'requirements.txt', 'wheels'], - 'applications': [Application('main.py', 'asteroids')], + 'gui_apps': { + 'asteroids': 'main.py', + }, 'deploy_platforms': [ 'manylinux1_x86_64', 'macosx_10_6_x86_64', From b8ee352ebb64bfae668506436dc170227ae4b9c1 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 28 Jan 2017 20:44:26 -0800 Subject: [PATCH 097/424] deploy-ng: Create entry_points in makewheel for dist commands This allows setuptools to automatically find build_p3d and bdist_p3d_archive. NOTE: Importing direct.showutil.dist.setup still works for installs that are not using wheels. --- makepanda/makewheel.py | 3 +++ samples/asteroids/setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index e485413926..ce4a55227f 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -577,6 +577,9 @@ def makewheel(version, output_dir, platform=default_platform): funcname = basename.replace('-', '_') entry_points += '{0} = panda3d_tools:{1}\n'.format(basename, funcname) tools_init += '{0} = lambda: _exec_tool({1!r})\n'.format(funcname, file) + entry_points += '[distutils.commands]\n' + entry_points += 'build_p3d = direct.showutil.dist:build_p3d\n' + entry_points += 'bdist_p3d_archive = direct.showutil.dist:bdist_p3d_archive\n' whl.write_file_data('panda3d_tools/__init__.py', PANDA3D_TOOLS_INIT.format(tools_init)) diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 16d433b149..e29f0ed104 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -1,4 +1,4 @@ -from direct.showutil.dist import * +from setuptools import setup setup( name="asteroids", From efd25e98a5efb858cbeab9640f3cff0082057118 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 28 Jan 2017 23:35:00 -0800 Subject: [PATCH 098/424] deploy-stub: Fix memory leaks --- pandatool/src/deploy-stub/deploy-stub.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index c0a128d424..3e066fe762 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -158,6 +158,7 @@ int main(int argc, char *argv[]) { #endif struct _frozen *_PyImport_FrozenModules; unsigned int listoff, modsoff, fsize, modsize, listsize, nummods, modidx; + int retval; FILE *runtime; #ifdef _WIN32 @@ -210,15 +211,25 @@ int main(int argc, char *argv[]) { moddef->size = codesize; } + fclose(runtime); + // Uncomment this to print out the read in module list - //for (unsigned int modidx = 0; modidx < nummods + 1; ++modidx) { + //for (modidx = 0; modidx < nummods; ++modidx) { // struct _frozen *moddef = &_PyImport_FrozenModules[modidx]; // printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size); //} - fclose(runtime); + // Run frozen application PyImport_FrozenModules = _PyImport_FrozenModules; - return Py_FrozenMain(argc, argv); + retval = Py_FrozenMain(argc, argv); + + // Free resources + free(modblob); + for (modidx = 0; modidx < nummods; ++modidx) { + struct _frozen *moddef = &_PyImport_FrozenModules[modidx]; + free((void*)moddef->name); + } + free(_PyImport_FrozenModules); } #ifdef WIN_UNICODE From bf5bddcbaf026404fd98c306cd6548e1b0eb38da Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 28 Jan 2017 23:42:03 -0800 Subject: [PATCH 099/424] deploy-ng: Remove previous build before making a new one --- direct/src/showutil/dist.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 5a0f10c109..891b3fe81f 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -69,8 +69,9 @@ class build_p3d(distutils.core.Command): for platform in platforms: builddir = os.path.join(self.build_base, platform) - if not os.path.exists(builddir): - distutils.dir_util.mkpath(builddir) + if os.path.exists(builddir): + distutils.dir_util.remove_tree(builddir) + distutils.dir_util.mkpath(builddir) if use_wheels: whldir = os.path.join(self.build_base, '__whl_cache__') From c0f0749c21f840e21a99e0b0bd01bf235746c018 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 28 Jan 2017 23:42:25 -0800 Subject: [PATCH 100/424] deploy-ng: Use os.path.normpath() to cleanup directory names --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 891b3fe81f..f059f2f172 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -234,7 +234,7 @@ class build_p3d(distutils.core.Command): distutils.file_util.copy_file(src, dst) for item in dirs[:]: - path = os.path.join(builddir, root, item) + path = os.path.normpath(os.path.join(builddir, root, item)) if item in ignore_copy_list: print("skipping", path) dirs.remove(item) From 9b07578d623a17e349b6338998fac3df9ccb069e Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 3 Feb 2017 12:25:58 +0100 Subject: [PATCH 101/424] deploy-ng: Linux: fix symbol error with static linking, fix omission of libCg --- direct/src/showutil/dist.py | 2 ++ makepanda/makepanda.py | 1 + 2 files changed, 3 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index f059f2f172..6879f59717 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -26,6 +26,8 @@ def find_packages(whlfile): return [ i for i in filelist if '.so.' in i or + #TODO: find a better way to exclude deploy_libs from this. + (i.endswith('.so') and not i.startswith('deploy_libs/')) or i.endswith('.dll') or i.endswith('.dylib') or 'libpandagl' in i or diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 7d6d1fae69..e7502a7828 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6394,6 +6394,7 @@ if PkgSkip("PYTHON") == 0: # Setup rpath so libs can be found in the same directory as the deployed game LibName('DEPLOYSTUB', "-Wl,-rpath,\$ORIGIN") LibName('DEPLOYSTUB', "-Wl,-z,origin") + LibName('DEPLOYSTUB', "-rdynamic") TargetAdd('deploy-stub.exe', input='deploy-stub.obj') if GetTarget() == 'windows': TargetAdd('deploy-stub.exe', input='frozen_dllmain.obj') From f1e1c2b891fbfb19ae7e4a277390c4db21018206 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Wed, 8 Feb 2017 00:16:06 +0100 Subject: [PATCH 102/424] Fixes for device button handling Made State enum accessible from python side Fixed missing state changing in evdevInputDevice class --- panda/src/device/evdevInputDevice.cxx | 15 +++++++++++++++ panda/src/device/inputDevice.h | 3 +-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 7a71e5f222..ca2247c012 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -574,6 +574,21 @@ process_events() { case EV_KEY: button = map_button(code); _button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time)); + // set the buttons state + State state; + if (events[i].value) { + state = S_down; + } else { + state = S_up; + } + for (int i = 0; i < _buttons.size(); ++i) { + if (get_button(i).handle == button) { + //NOTE: set_button_state doesn't work correct here. + //set_button_state(i, state); + _buttons[i].state = state; + break; + } + } break; default: diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index d964b80781..0850d4a1d3 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -243,14 +243,13 @@ protected: PT(ButtonEventList) _button_events; PT(PointerEventList) _pointer_events; -public: +PUBLISHED: enum State { S_unknown, S_up, S_down }; -PUBLISHED: class ButtonState { public: INLINE ButtonState(); From 66ef8f7c2a6463315671606ed0171922f761caaa Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 11 Feb 2017 16:04:48 -0800 Subject: [PATCH 103/424] deploy-ng: Rename build_p3d to build_apps and bdist_p3d_archive to bdist_apps --- direct/src/showutil/dist.py | 12 ++++++------ makepanda/makewheel.py | 4 ++-- samples/asteroids/setup.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 6879f59717..63badf8fd9 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -35,7 +35,7 @@ def find_packages(whlfile): ] -class build_p3d(distutils.core.Command): +class build_apps(distutils.core.Command): user_options = [] # TODO def initialize_options(self): @@ -255,7 +255,7 @@ class build_p3d(distutils.core.Command): distutils.file_util.copy_file(src, dst) -class bdist_p3d_archive(distutils.core.Command): +class bdist_apps(distutils.core.Command): user_options = [] def initialize_options(self): @@ -265,14 +265,14 @@ class bdist_p3d_archive(distutils.core.Command): pass def run(self): - build_cmd = self.get_finalized_command('build_p3d') + build_cmd = self.get_finalized_command('build_apps') if not build_cmd.deploy_platforms: platforms = [p3d.PandaSystem.get_platform()] else: platforms = build_cmd.deploy_platforms build_base = build_cmd.build_base - self.run_command('build_p3d') + self.run_command('build_apps') os.chdir(build_base) for platform in platforms: @@ -292,6 +292,6 @@ class bdist_p3d_archive(distutils.core.Command): def setup(**attrs): commandClasses = attrs.setdefault("cmdclass", {}) - commandClasses['build_p3d'] = build_p3d - commandClasses['bdist_p3d_archive'] = bdist_p3d_archive + commandClasses['build_apps'] = build_apps + commandClasses['bdist_apps'] = bdist_apps distutils.core.setup(**attrs) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index ce4a55227f..2fd7efea02 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -578,8 +578,8 @@ def makewheel(version, output_dir, platform=default_platform): entry_points += '{0} = panda3d_tools:{1}\n'.format(basename, funcname) tools_init += '{0} = lambda: _exec_tool({1!r})\n'.format(funcname, file) entry_points += '[distutils.commands]\n' - entry_points += 'build_p3d = direct.showutil.dist:build_p3d\n' - entry_points += 'bdist_p3d_archive = direct.showutil.dist:bdist_p3d_archive\n' + entry_points += 'build_apps = direct.showutil.dist:build_apps\n' + entry_points += 'bdist_apps = direct.showutil.dist:bdist_apps\n' whl.write_file_data('panda3d_tools/__init__.py', PANDA3D_TOOLS_INIT.format(tools_init)) diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index e29f0ed104..c54f43a071 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -3,7 +3,7 @@ from setuptools import setup setup( name="asteroids", options = { - 'build_p3d': { + 'build_apps': { 'directories': ['.'], 'exclude_paths': ['build', 'setup.py', 'requirements.txt', 'wheels'], 'gui_apps': { From dcb743c30d9ed04be88e3d6ae6dce8d63dd0e4f3 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 11 Feb 2017 18:48:56 -0800 Subject: [PATCH 104/424] deploy-ng: Combine build_apps files and directories options into a single copy_paths copy_paths is a list of strings and two item sequences. If a copy_paths element is a single string, it is treated as both the source and destination paths. If the element is a sequence, the first element is the source and the second is the destination. NOTE: exclude_paths now uses file globs TODO: Support globs for copy_paths --- direct/src/showutil/dist.py | 85 ++++++++++++++++++++----------------- samples/asteroids/setup.py | 4 +- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 63badf8fd9..65c3696f02 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -13,6 +13,9 @@ from direct.showutil import FreezeTool import panda3d.core as p3d +if 'basestring' not in globals(): + basestring = str + # TODO replace with Packager def find_packages(whlfile): @@ -42,8 +45,7 @@ class build_apps(distutils.core.Command): self.build_base = os.path.join(os.getcwd(), 'build') self.gui_apps = {} self.console_apps = {} - self.directories = [] - self.files = [] + self.copy_paths = [] self.exclude_paths = [] self.exclude_modules = [] self.deploy_platforms = [] @@ -211,48 +213,55 @@ class build_apps(distutils.core.Command): ignore_copy_list = [ '__pycache__', ] + list(freezer_modules) + self.exclude_paths + list(self.gui_apps.values()) + list(self.console_apps.values()) + ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] - for copydir in self.directories: - for root, dirs, files in os.walk(copydir): - for item in files: - src = os.path.join(root, item) - dst = os.path.normpath(os.path.join(builddir, root, item)) + def copy_file(src, dst): + src = os.path.normpath(src) + dst = os.path.normpath(dst) - if item in ignore_copy_list: - print("skipping", src) - continue + dst_dir = os.path.dirname(dst) + if not os.path.exists(dst_dir): + distutils.dir_util.mkpath(dst_dir) - ext = os.path.splitext(src)[1] - dst_root = os.path.splitext(dst)[0] + ext = os.path.splitext(src)[1] + dst_root = os.path.splitext(dst)[0] - if ext in self.build_scripts: - dst_ext, script = self.build_scripts[ext] - dst = dst_root + dst_ext - script = script.format(src, dst) - print("using script:", script) - subprocess.call(script.split()) - else: - #print("Copy file", src, dst) - distutils.file_util.copy_file(src, dst) + for pattern in ignore_copy_list: + #print("check ignore:", pattern, src, pattern.matches(src)) + if pattern.matches(src): + print("skipping file", src) + return - for item in dirs[:]: - path = os.path.normpath(os.path.join(builddir, root, item)) - if item in ignore_copy_list: - print("skipping", path) - dirs.remove(item) - else: - print("making directory", path) - distutils.dir_util.mkpath(path) - - # Copy extra files - for extra in self.files: - if len(extra) == 2: - src, dst = extra - dst = os.path.join(builddir, dst) + if ext in self.build_scripts: + dst_ext, script = self.build_scripts[ext] + dst = dst_root + dst_ext + script = script.format(src, dst) + print("using script:", script) + subprocess.call(script.split()) else: - src = extra - dst = builddir - distutils.file_util.copy_file(src, dst) + #print("Copy file", src, dst) + distutils.file_util.copy_file(src, dst) + + def copy_dir(src, dst): + for item in os.listdir(src): + s = os.path.join(src, item) + d = os.path.join(dst, item) + if os.path.isfile(s): + copy_file(s, d) + else: + copy_dir(s, d) + + for path in self.copy_paths: + if isinstance(path, basestring): + src = dst = path + else: + src, dst = path + dst = os.path.join(builddir, dst) + + if os.path.isfile(src): + copy_file(src, dst) + else: + copy_dir(src, dst) class bdist_apps(distutils.core.Command): diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index c54f43a071..7ee64d2b0b 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -4,8 +4,8 @@ setup( name="asteroids", options = { 'build_apps': { - 'directories': ['.'], - 'exclude_paths': ['build', 'setup.py', 'requirements.txt', 'wheels'], + 'copy_paths': ['.'], + 'exclude_paths': ['build/*', 'setup.py', 'requirements.txt', 'wheels/*', '*.swp'], 'gui_apps': { 'asteroids': 'main.py', }, From 887278f306c10fff7e5e2217e7a1c070ec3e4ba6 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 15 Feb 2017 18:05:30 -0800 Subject: [PATCH 105/424] deploy-ng setuptools: Change build_apps.exclude_modules to a dict and add include_modules The key of both of these dictionaries is the app this affects (must match a key in gui_apps or console_apps). A key of '*' affects all applications. --- direct/src/showutil/dist.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 65c3696f02..70cc88c99a 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -47,7 +47,8 @@ class build_apps(distutils.core.Command): self.console_apps = {} self.copy_paths = [] self.exclude_paths = [] - self.exclude_modules = [] + self.include_modules = {} + self.exclude_modules = {} self.deploy_platforms = [] self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] @@ -121,7 +122,9 @@ class build_apps(distutils.core.Command): def create_runtime(appname, mainscript, use_console): freezer = FreezeTool.Freezer(platform=platform) freezer.addModule('__main__', filename=mainscript) - for exmod in self.exclude_modules: + for incmod in self.include_modules.get(appname, []) + self.include_modules.get('*', []): + freezer.addModule(incmod) + for exmod in self.exclude_modules.get(appname, []) + self.exclude_modules.get('*', []): freezer.excludeModule(exmod) freezer.done(addStartupModules=True) From 8fa6026a68a2afe0182da2a7ce5a3e980042cd90 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 4 Apr 2017 21:25:58 -0700 Subject: [PATCH 106/424] deploy-ng: Play nicer with Py2 * ignore pyc files * import print_function from __future__ --- direct/src/showutil/dist.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 70cc88c99a..8e49a35e47 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import collections import os import pip @@ -215,6 +217,7 @@ class build_apps(distutils.core.Command): # Copy Game Files ignore_copy_list = [ '__pycache__', + '*.pyc', ] + list(freezer_modules) + self.exclude_paths + list(self.gui_apps.values()) + list(self.console_apps.values()) ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] From 923ff14d4146137f6f99a41a7e36edfb5f9a0c55 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 4 Apr 2017 21:36:54 -0700 Subject: [PATCH 107/424] deploy-ng: Apply ignore rules before creating directories --- direct/src/showutil/dist.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 8e49a35e47..de04f19929 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -225,6 +225,13 @@ class build_apps(distutils.core.Command): src = os.path.normpath(src) dst = os.path.normpath(dst) + for pattern in ignore_copy_list: + #print("check ignore:", pattern, src, pattern.matches(src)) + if pattern.matches(src): + print("skipping file", src) + return + + dst_dir = os.path.dirname(dst) if not os.path.exists(dst_dir): distutils.dir_util.mkpath(dst_dir) @@ -232,12 +239,6 @@ class build_apps(distutils.core.Command): ext = os.path.splitext(src)[1] dst_root = os.path.splitext(dst)[0] - for pattern in ignore_copy_list: - #print("check ignore:", pattern, src, pattern.matches(src)) - if pattern.matches(src): - print("skipping file", src) - return - if ext in self.build_scripts: dst_ext, script = self.build_scripts[ext] dst = dst_root + dst_ext From 003348ff10fd001b39ef15aebf75af303d2e1465 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 11 Apr 2017 20:14:46 -0700 Subject: [PATCH 108/424] deploy-ng: Fix issue when builtin modules list differs between host and target --- direct/src/showutil/dist.py | 46 +++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index de04f19929..0ae8e0bad2 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -156,21 +156,43 @@ class build_apps(distutils.core.Command): create_runtime(appname, scriptname, True) # Copy extension modules + whl_modules = [] + whl_modules_ext = '' + if use_wheels: + # Get the module libs + whl_modules = [ + i.replace('deploy_libs/', '') for i in p3dwhl.namelist() if i.startswith('deploy_libs/') + ] + + # Pull off extension + if whl_modules: + whl_modules_ext = '.'.join(whl_modules[0].split('.')[1:]) + whl_modules = [i.split('.')[0] for i in whl_modules] + + for module, source_path in freezer_extras: - if source_path is None: - # Built-in module. - continue + if module == 'time': + source_path = None - # Rename panda3d/core.pyd to panda3d.core.pyd - basename = os.path.basename(source_path) - if '.' in module: - basename = module.rsplit('.', 1)[0] + '.' + basename + if source_path is not None: + # Rename panda3d/core.pyd to panda3d.core.pyd + basename = os.path.basename(source_path) + if '.' in module: + basename = module.rsplit('.', 1)[0] + '.' + basename + + # Remove python version string + if sys.version_info >= (3, 0): + parts = basename.split('.') + parts = parts[:-2] + parts[-1:] + basename = '.'.join(parts) + else: + # Builtin module, but might not be builtin in wheel libs, so double check + if module in whl_modules: + source_path = '{}/deploy_libs/{}.{}'.format(p3dwhlfn, module, whl_modules_ext) + basename = os.path.basename(source_path) + else: + continue - # Remove python version string - if sys.version_info >= (3, 0): - parts = basename.split('.') - parts = parts[:-2] + parts[-1:] - basename = '.'.join(parts) target_path = os.path.join(builddir, basename) if '.whl/' in source_path: From 9e9aebad58210c8765c3a4b4491ac0d6694621fe Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 11 Apr 2017 20:15:41 -0700 Subject: [PATCH 109/424] deploy-ng asteroid sample: Add --pre to requirements.txt This forces pip to prefer 1.10 builds even if it finds 1.9 builds (e.g., for Python2) --- samples/asteroids/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/asteroids/requirements.txt b/samples/asteroids/requirements.txt index 71b51750d6..e596b96a23 100644 --- a/samples/asteroids/requirements.txt +++ b/samples/asteroids/requirements.txt @@ -1,2 +1,3 @@ -f ./wheels +--pre panda3d From 82f08b1251fbba9b7cd62d05bf5f7edf83ecfe03 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 11 Apr 2017 20:18:14 -0700 Subject: [PATCH 110/424] deploy-ng: Remove debug code --- direct/src/showutil/dist.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 0ae8e0bad2..ba3e3d2f91 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -171,9 +171,6 @@ class build_apps(distutils.core.Command): for module, source_path in freezer_extras: - if module == 'time': - source_path = None - if source_path is not None: # Rename panda3d/core.pyd to panda3d.core.pyd basename = os.path.basename(source_path) From d1241797b97c925249583a17d2e7ac0aed438946 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 20 Apr 2017 17:48:07 -0700 Subject: [PATCH 111/424] deploy-ng: Fix swapped console and gui apps on Windows --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index ba3e3d2f91..0dd3772389 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -132,7 +132,7 @@ class build_apps(distutils.core.Command): stub_name = 'deploy-stub' if platform.startswith('win'): - if use_console: + if not use_console: stub_name = 'deploy-stubw' stub_name += '.exe' From c1f05453598f64ce6039d7aaecd6f899736378bf Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 20 Apr 2017 17:53:28 -0700 Subject: [PATCH 112/424] deploy-ng sample: Use buildbot index instead of a wheels folder --- samples/asteroids/requirements.txt | 3 +-- samples/asteroids/wheels/readme.txt | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 samples/asteroids/wheels/readme.txt diff --git a/samples/asteroids/requirements.txt b/samples/asteroids/requirements.txt index e596b96a23..a8e341eee1 100644 --- a/samples/asteroids/requirements.txt +++ b/samples/asteroids/requirements.txt @@ -1,3 +1,2 @@ --f ./wheels ---pre +--pre --extra-index-url https://archive.panda3d.org/branches/deploy-ng panda3d diff --git a/samples/asteroids/wheels/readme.txt b/samples/asteroids/wheels/readme.txt deleted file mode 100644 index d0ea2e0941..0000000000 --- a/samples/asteroids/wheels/readme.txt +++ /dev/null @@ -1 +0,0 @@ -Put whl files in this folder to get picked up by the build step From 61513593c575d94948013302d56e5129ce33fd7b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 20 Apr 2017 18:01:07 -0700 Subject: [PATCH 113/424] deploy-ng: Fix exclude paths on Windows --- direct/src/showutil/dist.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 0dd3772389..029656086b 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -245,8 +245,10 @@ class build_apps(distutils.core.Command): dst = os.path.normpath(dst) for pattern in ignore_copy_list: - #print("check ignore:", pattern, src, pattern.matches(src)) - if pattern.matches(src): + # Normalize file paths across platforms + path = p3d.Filename.from_os_specific(src).get_fullpath() + #print("check ignore:", pattern, src, pattern.matches(path)) + if pattern.matches(path): print("skipping file", src) return From 99c3e16dff52489a4bc3d7a60925dcf875ae7ddf Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 25 Apr 2017 22:00:16 -0700 Subject: [PATCH 114/424] deploy-ng: Do not copy py files These should all be frozen. If not, we need to fix our module finder. --- direct/src/showutil/dist.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 029656086b..547fee29c2 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -237,7 +237,9 @@ class build_apps(distutils.core.Command): ignore_copy_list = [ '__pycache__', '*.pyc', - ] + list(freezer_modules) + self.exclude_paths + list(self.gui_apps.values()) + list(self.console_apps.values()) + '*.py', + ] + ignore_copy_list += self.exclude_paths ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] def copy_file(src, dst): From f081a8f0522f80ac0c95acdad7e719bc506dfa47 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 25 Apr 2017 22:01:28 -0700 Subject: [PATCH 115/424] deploy-ng: Do not create/copy empty directories This includes any directories that have all contents ignored. --- direct/src/showutil/dist.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 547fee29c2..881adff4ab 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -242,18 +242,29 @@ class build_apps(distutils.core.Command): ignore_copy_list += self.exclude_paths ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] + def check_pattern(src): + for pattern in ignore_copy_list: + # Normalize file paths across platforms + path = p3d.Filename.from_os_specific(src).get_fullpath() + #print("check ignore:", pattern, src, pattern.matches(path)) + if pattern.matches(path): + return True + return False + + def dir_has_files(directory): + files = [ + i for i in os.listdir(directory) + if not check_pattern(os.path.join(directory, i)) + ] + return bool(files) + def copy_file(src, dst): src = os.path.normpath(src) dst = os.path.normpath(dst) - for pattern in ignore_copy_list: - # Normalize file paths across platforms - path = p3d.Filename.from_os_specific(src).get_fullpath() - #print("check ignore:", pattern, src, pattern.matches(path)) - if pattern.matches(path): - print("skipping file", src) - return - + if check_pattern(src): + print("skipping file", src) + return dst_dir = os.path.dirname(dst) if not os.path.exists(dst_dir): @@ -278,7 +289,7 @@ class build_apps(distutils.core.Command): d = os.path.join(dst, item) if os.path.isfile(s): copy_file(s, d) - else: + elif dir_has_files(s): copy_dir(s, d) for path in self.copy_paths: From 2b348b614e66dce1764b1fcee8ab31bc082be733 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 7 May 2017 18:43:35 -0700 Subject: [PATCH 116/424] deploy-ng: Allow running build_scripts on dotfiles (e.g., .pman) --- direct/src/showutil/dist.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 881adff4ab..306e827cca 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -271,6 +271,8 @@ class build_apps(distutils.core.Command): distutils.dir_util.mkpath(dst_dir) ext = os.path.splitext(src)[1] + if not ext: + ext = os.path.basename(src) dst_root = os.path.splitext(dst)[0] if ext in self.build_scripts: From 961487cd3d7384ffeb7f61948ef889289a31ec34 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 22 May 2017 21:52:51 -0700 Subject: [PATCH 117/424] deploy-ng: Fix to allow namespace packages --- direct/src/showutil/FreezeTool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 932890d7c2..237415c190 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -713,7 +713,7 @@ class Freezer: # special path mangling.) for moduleName, module in list(sys.modules.items()): if module and hasattr(module, '__path__'): - path = getattr(module, '__path__') + path = list(getattr(module, '__path__')) if path: modulefinder.AddPackagePath(moduleName, path[0]) From f374c0bc4af3a2051807b1567fe4dddacd645213 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 23 May 2017 20:35:28 -0700 Subject: [PATCH 118/424] deploy-ng: Fix for missing modules when building on Ubuntu (and probably other distros) Ubuntu has different builtins than manylinux1, and we need to make sure we still copy shared objects when the host marks a module as builtin and manylinux1 has an so for the module. --- direct/src/showutil/dist.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 306e827cca..698d4969e2 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -169,7 +169,12 @@ class build_apps(distutils.core.Command): whl_modules_ext = '.'.join(whl_modules[0].split('.')[1:]) whl_modules = [i.split('.')[0] for i in whl_modules] + # Make sure to copy any builtins that have shared objects in the deploy libs + for mod in freezer_modules: + if mod in whl_modules: + freezer_extras.add((mod, None)) + # Copy any shared objects we need for module, source_path in freezer_extras: if source_path is not None: # Rename panda3d/core.pyd to panda3d.core.pyd From 26e7c3c52b5b90f071b3bb9f9f3040a05ae6a43a Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 27 May 2017 15:30:19 -0700 Subject: [PATCH 119/424] deploy-ng: Add more enocdings modules to FreezeTool.startupModules --- direct/src/showutil/FreezeTool.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 237415c190..116883e768 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -31,11 +31,16 @@ isDebugBuild = (python.lower().endswith('_d')) # These are modules that Python always tries to import up-front. They # must be frozen in any main.exe. startupModules = [ + 'encodings', 'encodings.aliases', 'encodings.undefined', 'encodings.ascii', 'encodings.cp1252', 'encodings.latin_1', 'encodings.utf_8', 'encodings.mbcs', 'encodings.cp850', 'encodings.cp437', 'imp', ] if sys.version_info >= (3, 0): + # Modules specific to Python 3 startupModules += ['io', 'marshal', 'importlib.machinery', 'importlib.util'] +else: + # Modules specific to Python 2 + startupModules += ['encodings.string_escape'] # These are some special init functions for some built-in Python modules that # deviate from the standard naming convention. A value of None means that a From db0267abcd8440df4f0faa842485aa6a73675321 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 27 May 2017 15:31:22 -0700 Subject: [PATCH 120/424] deploy-ng: Specify an abi tag when downloading wheels for other platforms with pip download --- direct/src/showutil/dist.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 698d4969e2..18f6d19781 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -82,6 +82,10 @@ class build_apps(distutils.core.Command): if use_wheels: whldir = os.path.join(self.build_base, '__whl_cache__') + abi_tag = pip.pep425tags.get_abi_tag() + + if 'u' in abi_tag and not platform.startswith('manylinux'): + abi_tag = abi_tag.replace('u', '') pip_args = [ 'download', @@ -89,6 +93,7 @@ class build_apps(distutils.core.Command): '-r', self.requirements_path, '--only-binary', ':all:', '--platform', platform, + '--abi', abi_tag ] for index in self.pypi_extra_indexes: From ed5b74ab54ddd349ab2ece72c3b545bda81e3068 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 31 May 2017 01:35:33 +0200 Subject: [PATCH 121/424] makepanda: allow using deploy-ng with build run from built dir --- makepanda/makepanda.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index b08c7ee808..404bbbf551 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2758,6 +2758,36 @@ if not PkgSkip("PYTHON"): ConditionalWriteFile(GetOutputDir() + '/pandac/extension_native_helpers.py', exthelpers_code) ConditionalWriteFile(GetOutputDir() + '/pandac/__init__.py', '') +########################################################################################## +# +# Write the dist-info directory. +# +########################################################################################## + +# This is just some basic stuff since setuptools just needs this file to +# exist, otherwise it will not read the entry_points.txt file. Maybe we will +# eventually want to merge this with the metadata generator in makewheel.py. +METADATA = """Metadata-Version: 2.0 +Name: Panda3D +Version: {version} +License: BSD +Home-page: https://www.panda3d.org/ +Author: Panda3D Team +Author-email: etc-panda3d@lists.andrew.cmu.edu +""" + +ENTRY_POINTS = """[distutils.commands] +build_apps = direct.showutil.dist:build_apps +bdist_apps = direct.showutil.dist:bdist_apps +""" + +if not PkgSkip("DIRECT"): + dist_dir = os.path.join(GetOutputDir(), 'panda3d.dist-info') + MakeDirectory(dist_dir) + + ConditionalWriteFile(os.path.join(dist_dir, 'METADATA'), METADATA.format(version=VERSION)) + ConditionalWriteFile(os.path.join(dist_dir, 'entry_points.txt'), ENTRY_POINTS) + ########################################################################################## # # Generate the PRC files into the ETC directory. From a686f501b6e4f21e5e727ea1500555e8e481c9ab Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 31 May 2017 01:59:43 +0200 Subject: [PATCH 122/424] deploy-ng: fix .so modules not being picked up on macOS --- direct/src/showutil/FreezeTool.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 116883e768..9484c72b9c 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -731,7 +731,7 @@ class Freezer: self.moduleSuffixes += [ ('.cpython-{0}{1}m-x86_64-linux-gnu.so'.format(*sys.version_info), 'rb', 3), ('.cpython-{0}{1}m-i686-linux-gnu.so'.format(*sys.version_info), 'rb', 3), - ('.abi{}.so'.format(sys.version_info[0]), 'rb', 3), + ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3), ('.so', 'rb', 3), ] elif 'win' in self.platform: @@ -743,6 +743,8 @@ class Freezer: elif 'mac' in self.platform: self.moduleSuffixes += [ ('.cpython-{0}{1}m-darwin.so'.format(*sys.version_info), 'rb', 3), + ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3), + ('.so', 'rb', 3), ] else: print("Unknown platform %s" % (self.platform)) From 55ce23fbbad384a2c78405a28fc8b91f73693085 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 31 May 2017 02:08:30 +0200 Subject: [PATCH 123/424] deploy-ng: only include libraries referenced by used modules --- direct/src/showutil/FreezeTool.py | 9 +- direct/src/showutil/dist.py | 694 ++++++++++++++++-------- direct/src/showutil/pefile.py | 863 ++++++++++++++++++++++++++++++ 3 files changed, 1326 insertions(+), 240 deletions(-) create mode 100755 direct/src/showutil/pefile.py diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 9484c72b9c..d9f63d260b 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -652,7 +652,7 @@ class Freezer: return 'ModuleDef(%s)' % (', '.join(args)) def __init__(self, previous = None, debugLevel = 0, - platform = None): + platform = None, path=None): # Normally, we are freezing for our own platform. Change this # if untrue. self.platform = platform or PandaSystem.getPlatform() @@ -663,6 +663,10 @@ class Freezer: # default object will be created when it is needed. self.cenv = None + # This is the search path to use for Python modules. Leave it + # to the default value of None to use sys.path. + self.path = path + # The filename extension to append to the source file before # compiling. self.sourceExtension = '.c' @@ -999,7 +1003,7 @@ class Freezer: else: includes.append(mdef) - self.mf = PandaModuleFinder(excludes = list(excludeDict.keys()), suffixes=self.moduleSuffixes) + self.mf = PandaModuleFinder(excludes=list(excludeDict.keys()), suffixes=self.moduleSuffixes, path=self.path) # Attempt to import the explicit modules into the modulefinder. @@ -1672,6 +1676,7 @@ class Freezer: f.write(struct.pack('= (3, 0): - parts = basename.split('.') - parts = parts[:-2] + parts[-1:] - basename = '.'.join(parts) - else: - # Builtin module, but might not be builtin in wheel libs, so double check - if module in whl_modules: - source_path = '{}/deploy_libs/{}.{}'.format(p3dwhlfn, module, whl_modules_ext) - basename = os.path.basename(source_path) - else: - continue - - - target_path = os.path.join(builddir, basename) - if '.whl/' in source_path: - # This was found in a wheel, extract it - whl, wf = source_path.split('.whl/') - whl += '.whl' - whlfile = whlfiles[whl] - print("copying {} -> {}".format(os.path.join(whl, wf), target_path)) - with open(target_path, 'wb') as f: - f.write(whlfile.read(wf)) - else: - # Regular file, copy it - distutils.file_util.copy_file(source_path, target_path) - - # Find Panda3D libs - libs = find_packages(p3dwhl if use_wheels else None) - - # Copy Panda3D files - etcdir = os.path.join(builddir, 'etc') - if not use_wheels: - # Libs - for lib in libs: - target_path = os.path.join(builddir, os.path.basename(lib)) - if not os.path.islink(source_path): - distutils.file_util.copy_file(lib, target_path) - - # etc - dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) - libdir = os.path.dirname(dtool_fn.to_os_specific()) - src = os.path.join(libdir, '..', 'etc') - distutils.dir_util.copy_tree(src, etcdir) + for whl in wheelpaths: + if 'panda3d-' in whl: + p3dwhlfn = whl + p3dwhl = self._get_zip_file(p3dwhlfn) + break else: - distutils.dir_util.mkpath(etcdir) + raise RuntimeError("Missing panda3d wheel") - # Combine prc files with libs and copy the whole list - panda_files = libs + [i for i in p3dwhl.namelist() if i.endswith('.prc')] - for pf in panda_files: - dstdir = etcdir if pf.endswith('.prc') else builddir - target_path = os.path.join(dstdir, os.path.basename(pf)) - print("copying {} -> {}".format(os.path.join(p3dwhlfn, pf), target_path)) - with open(target_path, 'wb') as f: - f.write(p3dwhl.read(pf)) + #whlfiles = {whl: self._get_zip_file(whl) for whl in wheelpaths} - # Copy Game Files - ignore_copy_list = [ - '__pycache__', - '*.pyc', - '*.py', + # Add whl files to the path so they are picked up by modulefinder + for whl in wheelpaths: + path.insert(0, whl) + + # Add deploy_libs from panda3d whl to the path + path.insert(0, os.path.join(p3dwhlfn, 'deploy_libs')) + + # Create runtimes + freezer_extras = set() + freezer_modules = set() + def create_runtime(appname, mainscript, use_console): + freezer = FreezeTool.Freezer(platform=platform, path=path) + freezer.addModule('__main__', filename=mainscript) + for incmod in self.include_modules.get(appname, []) + self.include_modules.get('*', []): + freezer.addModule(incmod) + for exmod in self.exclude_modules.get(appname, []) + self.exclude_modules.get('*', []): + freezer.excludeModule(exmod) + freezer.done(addStartupModules=True) + + stub_name = 'deploy-stub' + if platform.startswith('win'): + if not use_console: + stub_name = 'deploy-stubw' + stub_name += '.exe' + + if use_wheels: + stub_file = p3dwhl.open('panda3d_tools/{0}'.format(stub_name)) + else: + dtool_path = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()).to_os_specific() + stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name) + stub_file = open(stub_path, 'rb') + + freezer.generateRuntimeFromStub(os.path.join(builddir, appname), stub_file) + stub_file.close() + + freezer_extras.update(freezer.extras) + freezer_modules.update(freezer.getAllModuleNames()) + + for appname, scriptname in self.gui_apps.items(): + create_runtime(appname, scriptname, False) + + for appname, scriptname in self.console_apps.items(): + create_runtime(appname, scriptname, True) + + # Copy extension modules + whl_modules = [] + whl_modules_ext = '' + if use_wheels: + # Get the module libs + whl_modules = [ + i.replace('deploy_libs/', '') for i in p3dwhl.namelist() if i.startswith('deploy_libs/') ] - ignore_copy_list += self.exclude_paths - ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] - def check_pattern(src): - for pattern in ignore_copy_list: - # Normalize file paths across platforms - path = p3d.Filename.from_os_specific(src).get_fullpath() - #print("check ignore:", pattern, src, pattern.matches(path)) - if pattern.matches(path): - return True - return False + # Pull off extension + if whl_modules: + whl_modules_ext = '.'.join(whl_modules[0].split('.')[1:]) + whl_modules = [i.split('.')[0] for i in whl_modules] - def dir_has_files(directory): - files = [ - i for i in os.listdir(directory) - if not check_pattern(os.path.join(directory, i)) - ] - return bool(files) + # Make sure to copy any builtins that have shared objects in the deploy libs + for mod in freezer_modules: + if mod in whl_modules: + freezer_extras.add((mod, None)) - def copy_file(src, dst): - src = os.path.normpath(src) - dst = os.path.normpath(dst) + #FIXME: this is a temporary hack to pick up libpandagl. + for lib in p3dwhl.namelist(): + if lib.startswith('panda3d/libpandagl.'): + source_path = os.path.join(p3dwhlfn, lib) + target_path = os.path.join(builddir, os.path.basename(lib)) + search_path = [os.path.dirname(source_path)] + self.copy_with_dependencies(source_path, target_path, search_path) - if check_pattern(src): - print("skipping file", src) + # Copy any shared objects we need + for module, source_path in freezer_extras: + if source_path is not None: + # Rename panda3d/core.pyd to panda3d.core.pyd + basename = os.path.basename(source_path) + if '.' in module: + basename = module.rsplit('.', 1)[0] + '.' + basename + + # Remove python version string + if sys.version_info >= (3, 0): + parts = basename.split('.') + parts = parts[:-2] + parts[-1:] + basename = '.'.join(parts) + else: + # Builtin module, but might not be builtin in wheel libs, so double check + if module in whl_modules: + source_path = '{0}/deploy_libs/{1}.{2}'.format(p3dwhlfn, module, whl_modules_ext) + basename = os.path.basename(source_path) + else: + continue + + # If this is a dynamic library, search for dependencies. + search_path = [os.path.dirname(source_path)] + if use_wheels: + search_path.append(os.path.join(p3dwhlfn, 'deploy_libs')) + + target_path = os.path.join(builddir, basename) + self.copy_with_dependencies(source_path, target_path, search_path) + + # Copy Panda3D files + etcdir = os.path.join(builddir, 'etc') + if not use_wheels: + # etc + dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) + libdir = os.path.dirname(dtool_fn.to_os_specific()) + src = os.path.join(libdir, '..', 'etc') + distutils.dir_util.copy_tree(src, etcdir) + else: + distutils.dir_util.mkpath(etcdir) + + # Combine prc files with libs and copy the whole list + panda_files = [i for i in p3dwhl.namelist() if i.endswith('.prc')] + for pf in panda_files: + dstdir = etcdir if pf.endswith('.prc') else builddir + target_path = os.path.join(dstdir, os.path.basename(pf)) + source_path = os.path.join(p3dwhlfn, pf) + + # If this is a dynamic library, search for dependencies. + search_path = [os.path.dirname(source_path)] + if use_wheels: + search_path.append(os.path.join(p3dwhlfn, 'deploy_libs')) + + self.copy_with_dependencies(source_path, target_path, search_path) + + # Copy Game Files + ignore_copy_list = [ + '__pycache__', + '*.pyc', + '*.py', + ] + ignore_copy_list += self.exclude_paths + ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] + + def check_pattern(src): + for pattern in ignore_copy_list: + # Normalize file paths across platforms + path = p3d.Filename.from_os_specific(src).get_fullpath() + #print("check ignore:", pattern, src, pattern.matches(path)) + if pattern.matches(path): + return True + return False + + def dir_has_files(directory): + files = [ + i for i in os.listdir(directory) + if not check_pattern(os.path.join(directory, i)) + ] + return bool(files) + + def copy_file(src, dst): + src = os.path.normpath(src) + dst = os.path.normpath(dst) + + if check_pattern(src): + print("skipping file", src) + return + + dst_dir = os.path.dirname(dst) + if not os.path.exists(dst_dir): + distutils.dir_util.mkpath(dst_dir) + + ext = os.path.splitext(src)[1] + if not ext: + ext = os.path.basename(src) + dst_root = os.path.splitext(dst)[0] + + if ext in self.build_scripts: + dst_ext, script = self.build_scripts[ext] + dst = dst_root + dst_ext + script = script.format(src, dst) + print("using script:", script) + subprocess.call(script.split()) + else: + #print("Copy file", src, dst) + distutils.file_util.copy_file(src, dst) + + def copy_dir(src, dst): + for item in os.listdir(src): + s = os.path.join(src, item) + d = os.path.join(dst, item) + if os.path.isfile(s): + copy_file(s, d) + elif dir_has_files(s): + copy_dir(s, d) + + for path in self.copy_paths: + if isinstance(path, basestring): + src = dst = path + else: + src, dst = path + dst = os.path.join(builddir, dst) + + if os.path.isfile(src): + copy_file(src, dst) + else: + copy_dir(src, dst) + + def add_dependency(self, name, target_dir, search_path, referenced_by): + """ Searches for the given DLL on the search path. If it exists, + copies it to the target_dir. """ + + if os.path.exists(os.path.join(target_dir, name)): + # We've already added it earlier. + return + + if name in self.exclude_dependencies: + return + + for dir in search_path: + source_path = os.path.join(dir, name) + + if os.path.isfile(source_path): + target_path = os.path.join(target_dir, name) + self.copy_with_dependencies(source_path, target_path, search_path) + return + + elif '.whl/' in source_path: + # Check whether the file exists inside the wheel. + whl, wf = source_path.split('.whl/') + whl += '.whl' + whlfile = self._get_zip_file(whl) + + # Look case-insensitively. + namelist = whlfile.namelist() + namelist_lower = [file.lower() for file in namelist] + + if wf.lower() in namelist_lower: + # We have a match. Change it to the correct case. + wf = namelist[namelist_lower.index(wf.lower())] + source_path = '/'.join((whl, wf)) + target_path = os.path.join(target_dir, os.path.basename(wf)) + self.copy_with_dependencies(source_path, target_path, search_path) return - dst_dir = os.path.dirname(dst) - if not os.path.exists(dst_dir): - distutils.dir_util.mkpath(dst_dir) + # If we didn't find it, look again, but case-insensitively. + name_lower = name.lower() - ext = os.path.splitext(src)[1] - if not ext: - ext = os.path.basename(src) - dst_root = os.path.splitext(dst)[0] + for dir in search_path: + if os.path.isdir(dir): + files = os.listdir(dir) + files_lower = [file.lower() for file in files] - if ext in self.build_scripts: - dst_ext, script = self.build_scripts[ext] - dst = dst_root + dst_ext - script = script.format(src, dst) - print("using script:", script) - subprocess.call(script.split()) - else: - #print("Copy file", src, dst) - distutils.file_util.copy_file(src, dst) + if name_lower in files_lower: + name = files[files_lower.index(name_lower)] + source_path = os.path.join(dir, name) + target_path = os.path.join(target_dir, name) + self.copy_with_dependencies(source_path, target_path, search_path) - def copy_dir(src, dst): - for item in os.listdir(src): - s = os.path.join(src, item) - d = os.path.join(dst, item) - if os.path.isfile(s): - copy_file(s, d) - elif dir_has_files(s): - copy_dir(s, d) + # Warn if we can't find it, but only once. + self.warn("could not find dependency {0} (referenced by {1})".format(name, referenced_by)) + self.exclude_dependencies.append(name) - for path in self.copy_paths: - if isinstance(path, basestring): - src = dst = path - else: - src, dst = path - dst = os.path.join(builddir, dst) + def copy_with_dependencies(self, source_path, target_path, search_path): + """ Copies source_path to target_path. It also scans source_path for + any dependencies, which are located along the given search_path and + copied to the same directory as target_path. - if os.path.isfile(src): - copy_file(src, dst) - else: - copy_dir(src, dst) + source_path may be located inside a .whl file. """ + + print("copying {0} -> {1}".format(os.path.relpath(source_path, self.build_base), os.path.relpath(target_path, self.build_base))) + + # Copy the file, and open it for analysis. + if '.whl/' in source_path: + # This was found in a wheel, extract it + whl, wf = source_path.split('.whl/') + whl += '.whl' + whlfile = self._get_zip_file(whl) + data = whlfile.read(wf) + with open(target_path, 'wb') as f: + f.write(data) + # Wrap the data in a BytesIO, since we need to be able to seek in + # the file; the stream returned by whlfile.open won't let us seek. + fp = io.BytesIO(data) + else: + # Regular file, copy it + distutils.file_util.copy_file(source_path, target_path) + fp = open(target_path, 'rb') + + # What kind of magic does the file contain? + deps = [] + magic = fp.read(4) + if magic.startswith(b'MZ'): + # It's a Windows DLL or EXE file. + pe = pefile.PEFile() + pe.read(fp) + deps = pe.imports + + elif magic == b'\x7FELF': + # Elf magic. Used on (among others) Linux and FreeBSD. + deps = self._read_dependencies_elf(fp) + + elif magic in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE', + b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'): + # A Mach-O file, as used on macOS. + deps = self._read_dependencies_macho(fp) + + elif magic in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA'): + # A fat file, containing multiple Mach-O binaries. In the future, + # we may want to extract the one containing the architecture we + # are building for. + deps = self._read_dependencies_fat(fp) + + # If we discovered any dependencies, recursively add those. + if deps: + target_dir = os.path.dirname(target_path) + base = os.path.basename(target_path) + for dep in deps: + self.add_dependency(dep, target_dir, search_path, base) + + def _read_dependencies_elf(self, elf): + """ Having read the first 4 bytes of the ELF file, fetches the + dependent libraries and returns those as a list. """ + + ident = elf.read(12) + + # Make sure we read in the correct endianness and integer size + byte_order = "<>"[ord(ident[1:2]) - 1] + elf_class = ord(ident[0:1]) - 1 # 0 = 32-bits, 1 = 64-bits + header_struct = byte_order + ("HHIIIIIHHHHHH", "HHIQQQIHHHHHH")[elf_class] + section_struct = byte_order + ("4xI8xIII8xI", "4xI16xQQI12xQ")[elf_class] + dynamic_struct = byte_order + ("iI", "qQ")[elf_class] + + type, machine, version, entry, phoff, shoff, flags, ehsize, phentsize, phnum, shentsize, shnum, shstrndx \ + = struct.unpack(header_struct, elf.read(struct.calcsize(header_struct))) + dynamic_sections = [] + string_tables = {} + + # Seek to the section header table and find the .dynamic section. + elf.seek(shoff) + for i in range(shnum): + type, offset, size, link, entsize = struct.unpack_from(section_struct, elf.read(shentsize)) + if type == 6 and link != 0: # DYNAMIC type, links to string table + dynamic_sections.append((offset, size, link, entsize)) + string_tables[link] = None + + # Read the relevant string tables. + for idx in string_tables.keys(): + elf.seek(shoff + idx * shentsize) + type, offset, size, link, entsize = struct.unpack_from(section_struct, elf.read(shentsize)) + if type != 3: continue + elf.seek(offset) + string_tables[idx] = elf.read(size) + + # Loop through the dynamic sections and rewrite it if it has an rpath/runpath. + needed = [] + rpath = [] + for offset, size, link, entsize in dynamic_sections: + elf.seek(offset) + data = elf.read(entsize) + tag, val = struct.unpack_from(dynamic_struct, data) + + # Read tags until we find a NULL tag. + while tag != 0: + if tag == 1: # A NEEDED entry. Read it from the string table. + string = string_tables[link][val : string_tables[link].find(b'\0', val)] + needed.append(string.decode('utf-8')) + + elif tag == 15 or tag == 29: + # An RPATH or RUNPATH entry. + string = string_tables[link][val : string_tables[link].find(b'\0', val)] + rpath += string.split(b':') + + data = elf.read(entsize) + tag, val = struct.unpack_from(dynamic_struct, data) + elf.close() + + #TODO: should we respect the RPATH? Clear it? Warn about it? + return needed + + def _read_dependencies_macho(self, fp): + """ Having read the first 4 bytes of the Mach-O file, fetches the + dependent libraries and returns those as a list. """ + + cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ + struct.unpack('I', fp.read(4))[0] + if num_fat == 0: + return [] + + # After the header we get a table of executables in this fat file, + # each one with a corresponding offset into the file. + # We are just interested in the first one for now. + cputype, cpusubtype, offset, size, align = \ + struct.unpack('>IIIII', fp.read(20)) + + # Add 4, since it expects we've already read the magic. + fp.seek(offset + 4) + return self._read_dependencies_macho(fp) class bdist_apps(distutils.core.Command): diff --git a/direct/src/showutil/pefile.py b/direct/src/showutil/pefile.py new file mode 100755 index 0000000000..20af71c4b7 --- /dev/null +++ b/direct/src/showutil/pefile.py @@ -0,0 +1,863 @@ +""" Tools for manipulating Portable Executable files. + +This can be used, for example, to extract a list of dependencies from an .exe +or .dll file, or to add version information and an icon resource to it. """ + +__all__ = ["PEFile"] + +from struct import Struct, unpack, pack, pack_into +from collections import namedtuple +from array import array +import time +from io import BytesIO +import sys + +if sys.version_info >= (3, 0): + unicode = str + unichr = chr + +# Define some internally used structures. +RVASize = namedtuple('RVASize', ('addr', 'size')) +impdirtab = namedtuple('impdirtab', ('lookup', 'timdat', 'forward', 'name', 'impaddr')) + + +def _unpack_zstring(mem, offs=0): + "Read a zero-terminated string from memory." + c = mem[offs] + str = "" + while c: + str += chr(c) + offs += 1 + c = mem[offs] + return str + +def _unpack_wstring(mem, offs=0): + "Read a UCS-2 string from memory." + name_len, = unpack('" % (self.name, self.vaddr, self.vaddr + self.vsize) + + def __gt__(self, other): + return self.vaddr > other.vaddr + + def __lt__(self, other): + return self.vaddr < other.vaddr + + +class DataResource(object): + """ A resource entry in the resource table. """ + + # Resource types. + cursor = 1 + bitmap = 2 + icon = 3 + menu = 4 + dialog = 5 + string = 6 + font_directory = 7 + font = 8 + accelerator = 9 + rcdata = 10 + message_table = 11 + cursor_group = 12 + icon_group = 14 + version = 16 + dlg_include = 17 + plug_play = 19 + vxd = 20 + animated_cursor = 21 + animated_icon = 22 + html = 23 + manifest = 24 + + def __init__(self): + self._ident = () + self.data = None + self.code_page = 0 + + def get_data(self): + if self.code_page: + return self.data.encode('cp%d' % self.code_page) + else: + return self.data + + +class IconGroupResource(object): + code_page = 0 + type = 14 + _entry = Struct('= 256: + colors = 0 + if width >= 256: + width = 0 + if height >= 256: + height = 0 + data += self._entry.pack(width, height, colors, planes, bpp, size, id) + return data + + def unpack_from(self, data, offs=0): + type, count = unpack(' 0: + self.signature = dwords[0] + if len(dwords) > 1: + self.struct_version = dwords[1] + if len(dwords) > 3: + self.file_version = \ + (int(dwords[2] >> 16), int(dwords[2] & 0xffff), + int(dwords[3] >> 16), int(dwords[3] & 0xffff)) + if len(dwords) > 5: + self.product_version = \ + (int(dwords[4] >> 16), int(dwords[4] & 0xffff), + int(dwords[5] >> 16), int(dwords[5] & 0xffff)) + if len(dwords) > 7: + self.file_flags_mask = dwords[6] + self.file_flags = dwords[7] + if len(dwords) > 8: + self.file_os = dwords[8] + if len(dwords) > 9: + self.file_type = dwords[9] + if len(dwords) > 10: + self.file_subtype = dwords[10] + if len(dwords) > 12: + self.file_date = (dwords[11], dwords[12]) + + while offset < length: + offset += self._unpack_info(self, data, offset) + + def __getitem__(self, key): + if key == 'StringFileInfo': + return self.string_info + elif key == 'VarFileInfo': + return self.var_info + else: + raise KeyError("%s does not exist" % (key)) + + def __contains__(self, key): + return key in ('StringFileInfo', 'VarFileInfo') + + def _unpack_info(self, dict, data, offset): + length, value_length, type = unpack(' 0 + end = offset + length + offset += 6 + key = "" + c, = unpack(' 0: + # It contains a value. + if type: + # It's a wchar array value. + value = u"" + c, = unpack('= key: + if key == idname: + return leaf + break + i += 1 + if not isinstance(key, int): + self._strings_size += _padded(len(key) * 2 + 2, 4) + leaf = ResourceTable(ident=self._ident + (key,)) + leaves.insert(i, (key, leaf)) + return leaf + + def __setitem__(self, key, value): + """ Adds the given item to the table. Maintains sort order. """ + if isinstance(key, int): + leaves = self._id_leaves + else: + leaves = self._name_leaves + + if not isinstance(value, ResourceTable): + self._descs_size += 16 + + value._ident = self._ident + (key,) + i = 0 + while i < len(leaves): + idname, leaf = leaves[i] + if idname >= key: + if key == idname: + if not isinstance(leaves[i][1], ResourceTable): + self._descs_size -= 16 + leaves[i] = (key, value) + return + break + i += 1 + if not isinstance(key, int): + self._strings_size += _padded(len(key) * 2 + 2, 4) + leaves.insert(i, (key, value)) + + def __len__(self): + return len(self._name_leaves) + len(self._id_leaves) + + def __iter__(self): + keys = [] + for name, leaf in self._name_leaves: + keys.append(name) + for id, leaf in self._id_leaves: + keys.append(id) + return iter(keys) + + def items(self): + return self._name_leaves + self._id_leaves + + def count_resources(self): + """Counts all of the resources.""" + count = 0 + for key, leaf in self._name_leaves + self._id_leaves: + if isinstance(leaf, ResourceTable): + count += leaf.count_resources() + else: + count += 1 + return count + + def get_nested_tables(self): + """Returns all tables in this table and subtables.""" + # First we yield child tables, then nested tables. This is the + # order in which pack_into assumes the tables will be written. + for key, leaf in self._name_leaves + self._id_leaves: + if isinstance(leaf, ResourceTable): + yield leaf + + for key, leaf in self._name_leaves + self._id_leaves: + if isinstance(leaf, ResourceTable): + for table in leaf.get_nested_tables(): + yield table + + def pack_header(self, data, offs): + self._header.pack_into(data, offs, self.flags, self.timdat, + self.version[0], self.version[1], + len(self._name_leaves), len(self._id_leaves)) + + def unpack_from(self, mem, addr=0, offs=0): + start = addr + offs + self.flags, self.timdat, majver, minver, nnames, nids = \ + self._header.unpack(mem[start:start+16]) + self.version = (majver, minver) + start += 16 + + # Subtables/entries specified by string name. + self._name_leaves = [] + for i in range(nnames): + name_p, data = unpack('= 1: + self.exp_rva = RVASize(*unpack('= 2: + self.imp_rva = RVASize(*unpack('= 3: + self.res_rva = RVASize(*unpack('= 4: + fp.seek((numrvas - 3) * 8, 1) + + # Loop through the sections to find the ones containing our tables. + self.sections = [] + for i in range(nscns): + section = Section() + section.read_header(fp) + self.sections.append(section) + + self.sections.sort() + + # Read the sections into some kind of virtual memory. + self.vmem = bytearray(self.sections[-1].vaddr + self.sections[-1].size) + + for section in self.sections: + fp.seek(section.offset) + fp.readinto(memoryview(self.vmem)[section.vaddr:section.vaddr+section.size]) + + # Read the import table. + start = self.imp_rva.addr + dir = impdirtab(*unpack('= 256: + continue + + xorsize = size + if xorsize % 4 != 0: + xorsize += 4 - (xorsize % 4) + andsize = (size + 7) >> 3 + if andsize % 4 != 0: + andsize += 4 - (andsize % 4) + datasize = 40 + 256 * 4 + (xorsize + andsize) * size + group.add_icon(size, size, 1, 8, datasize, id) + + buf = BytesIO() + icon._write_bitmap(buf, image, size, 8) + + res = DataResource() + res.data = buf.getvalue() + self.resources[3][id][1033] = res + id += 1 + + # And now the 24/32 bpp versions. + for size, image in images: + if size > 256: + continue + + # Calculate the size so we can write the offset within the file. + if image.hasAlpha(): + bpp = 32 + xorsize = size * 4 + else: + bpp = 24 + xorsize = size * 3 + (-(size * 3) & 3) + andsize = (size + 7) >> 3 + if andsize % 4 != 0: + andsize += 4 - (andsize % 4) + datasize = 40 + (xorsize + andsize) * size + + buf = BytesIO() + icon._write_bitmap(buf, image, size, bpp) + + res = DataResource() + res.data = buf.getvalue() + self.resources[3][id][1033] = res + group.add_icon(size, size, 1, bpp, datasize, id) + id += 1 + + def add_section(self, name, flags, data): + """ Adds a new section with the given name, flags and data. The + virtual address space is automatically resized to fit the new data. + + Returns the newly created Section object. """ + + if isinstance(name, unicode): + name = name.encode('ascii') + + section = Section() + section.name = name + section.flags = flags + + # Put it at the end of all the other sections. + section.offset = 0 + for s in self.sections: + section.offset = max(section.offset, s.offset + s.size) + + # Align the offset. + section.offset = _padded(section.offset, self.file_alignment) + + # Find a place to put it in the virtual address space. + section.vaddr = len(self.vmem) + align = section.vaddr % self.section_alignment + if align: + pad = self.section_alignment - align + self.vmem += bytearray(pad) + section.vaddr += pad + + section.vsize = len(data) + section.size = _padded(section.vsize, self.file_alignment) + self.vmem += data + self.sections.append(section) + + # Update the size tallies from the opthdr. + self.image_size += _padded(section.vsize, self.section_alignment) + if flags & 0x20: + self.code_size += section.size + if flags & 0x40: + self.initialized_size += section.size + if flags & 0x80: + self.uninitialized_size += section.size + + return section + + def add_version_info(self, file_ver, product_ver, data, lang=1033, codepage=1200): + """ Adds a version info resource to the file. """ + + if "FileVersion" not in data: + data["FileVersion"] = '.'.join(file_ver) + if "ProductVersion" not in data: + data["ProductVersion"] = '.'.join(product_ver) + + assert len(file_ver) == 4 + assert len(product_ver) == 4 + + res = VersionInfoResource() + res.file_version = file_ver + res.product_version = product_ver + res.string_info = { + "%04x%04x" % (lang, codepage): data + } + res.var_info = { + "Translation": bytearray(pack("= 3 + + fp.seek(self.rva_offset + 4) + if numrvas >= 1: + fp.write(pack('= 2: + fp.write(pack('= 3: + fp.write(pack('= 4: + fp.seek((numrvas - 3) * 8, 1) + + # Write the modified section headers. + for section in self.sections: + section.write_header(fp) + assert fp.tell() <= self.header_size + + # Write the section data of modified sections. + for section in self.sections: + if not section.modified: + continue + + fp.seek(section.offset) + size = min(section.vsize, section.size) + fp.write(self.vmem[section.vaddr:section.vaddr+size]) + + pad = section.size - size + assert pad >= 0 + if pad > 0: + fp.write(bytearray(pad)) + + section.modified = False From cfad049992f7a7e7f4ec0573f03175363c699409 Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Sat, 10 Jun 2017 21:59:18 +0200 Subject: [PATCH 124/424] __file__ usage in CommonFilters Removed __file__ usage due to unavailability in deploy-ng builds Moved Cg filters from sha files into separate python scripts Removed now obsolete loadShader function from CommonFilters --- direct/src/filter/CommonFilters.py | 35 ++++++++++--------- .../{filter-bloomi.sha => filterBloomI.py} | 4 ++- .../{filter-bloomx.sha => filterBloomX.py} | 2 ++ .../{filter-bloomy.sha => filterBloomY.py} | 2 ++ .../{filter-blurx.sha => filterBlurX.py} | 4 ++- .../{filter-blury.sha => filterBlurY.py} | 5 +-- .../filter/{filter-copy.sha => filterCopy.py} | 3 +- .../{filter-down4.sha => filterDown4.py} | 3 +- 8 files changed, 35 insertions(+), 23 deletions(-) rename direct/src/filter/{filter-bloomi.sha => filterBloomI.py} (99%) rename direct/src/filter/{filter-bloomx.sha => filterBloomX.py} (99%) rename direct/src/filter/{filter-bloomy.sha => filterBloomY.py} (98%) rename direct/src/filter/{filter-blurx.sha => filterBlurX.py} (94%) rename direct/src/filter/{filter-blury.sha => filterBlurY.py} (94%) rename direct/src/filter/{filter-copy.sha => filterCopy.py} (97%) rename direct/src/filter/{filter-down4.sha => filterDown4.py} (98%) diff --git a/direct/src/filter/CommonFilters.py b/direct/src/filter/CommonFilters.py index 9cfe72d987..e6b96f36f1 100644 --- a/direct/src/filter/CommonFilters.py +++ b/direct/src/filter/CommonFilters.py @@ -16,6 +16,13 @@ clunky approach. - Josh """ from .FilterManager import FilterManager +from .filterBloomI import BLOOM_I +from .filterBloomX import BLOOM_X +from .filterBloomY import BLOOM_Y +from .filterBlurX import BLUR_X +from .filterBlurY import BLUR_Y +from .filterCopy import COPY +from .filterDown4 import DOWN_4 from panda3d.core import LVecBase4, LPoint2 from panda3d.core import Filename from panda3d.core import AuxBitplaneAttrib @@ -102,12 +109,6 @@ class CommonFilters: self.task = None self.cleanup() - def loadShader(self, name): - fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), name) - fn = Filename.fromOsSpecific(fn) - fn.makeTrueCase() - return Shader.load(fn) - def cleanup(self): self.manager.cleanup() self.textures = {} @@ -184,9 +185,9 @@ class CommonFilters: self.blur.append(self.manager.renderQuadInto(colortex=blur0,div=2)) self.blur.append(self.manager.renderQuadInto(colortex=blur1)) self.blur[0].setShaderInput("src", self.textures["color"]) - self.blur[0].setShader(self.loadShader("filter-blurx.sha")) + self.blur[0].setShader(Shader.make(BLUR_X, Shader.SL_Cg)) self.blur[1].setShaderInput("src", blur0) - self.blur[1].setShader(self.loadShader("filter-blury.sha")) + self.blur[1].setShader(Shader.make(BLUR_Y, Shader.SL_Cg)) if ("AmbientOcclusion" in configuration): ssao0=self.textures["ssao0"] @@ -200,9 +201,9 @@ class CommonFilters: self.ssao[0].setShaderInput("random", loader.loadTexture("maps/random.rgb")) self.ssao[0].setShader(Shader.make(SSAO_BODY % configuration["AmbientOcclusion"].numsamples, Shader.SL_Cg)) self.ssao[1].setShaderInput("src", ssao0) - self.ssao[1].setShader(self.loadShader("filter-blurx.sha")) + self.ssao[1].setShader(Shader.make(BLUR_X, Shader.SL_Cg)) self.ssao[2].setShaderInput("src", ssao1) - self.ssao[2].setShader(self.loadShader("filter-blury.sha")) + self.ssao[2].setShader(Shader.make(BLUR_Y, Shader.SL_Cg)) if ("Bloom" in configuration): bloomconf = configuration["Bloom"] @@ -212,25 +213,25 @@ class CommonFilters: bloom3=self.textures["bloom3"] if (bloomconf.size == "large"): scale=8 - downsampler="filter-down4.sha" + downsampler=DOWN_4 elif (bloomconf.size == "medium"): scale=4 - downsampler="filter-copy.sha" + downsampler=COPY else: scale=2 - downsampler="filter-copy.sha" + downsampler=COPY self.bloom.append(self.manager.renderQuadInto(colortex=bloom0, div=2, align=scale)) self.bloom.append(self.manager.renderQuadInto(colortex=bloom1, div=scale, align=scale)) self.bloom.append(self.manager.renderQuadInto(colortex=bloom2, div=scale, align=scale)) self.bloom.append(self.manager.renderQuadInto(colortex=bloom3, div=scale, align=scale)) self.bloom[0].setShaderInput("src", self.textures["color"]) - self.bloom[0].setShader(self.loadShader("filter-bloomi.sha")) + self.bloom[0].setShader(Shader.make(BLOOM_I, Shader.SL_Cg)) self.bloom[1].setShaderInput("src", bloom0) - self.bloom[1].setShader(self.loadShader(downsampler)) + self.bloom[1].setShader(Shader.make(downsampler, Shader.SL_Cg)) self.bloom[2].setShaderInput("src", bloom1) - self.bloom[2].setShader(self.loadShader("filter-bloomx.sha")) + self.bloom[2].setShader(Shader.make(BLOOM_X, Shader.SL_Cg)) self.bloom[3].setShaderInput("src", bloom2) - self.bloom[3].setShader(self.loadShader("filter-bloomy.sha")) + self.bloom[3].setShader(Shader.make(BLOOM_Y, Shader.SL_Cg)) texcoords = {} texcoordPadding = {} diff --git a/direct/src/filter/filter-bloomi.sha b/direct/src/filter/filterBloomI.py similarity index 99% rename from direct/src/filter/filter-bloomi.sha rename to direct/src/filter/filterBloomI.py index 8b58c21669..6c44fb3ecb 100644 --- a/direct/src/filter/filter-bloomi.sha +++ b/direct/src/filter/filterBloomI.py @@ -1,3 +1,4 @@ +BLOOM_I = """ //Cg // // blend.rgb @@ -23,7 +24,7 @@ // trigger.y // // Must be equal to (1.0/(maxtrigger-mintrigger)) where -// +// // mintrigger is the minimum brightness to trigger a bloom, // and maxtrigger is the brightness at which the bloom // reaches maximum intensity. @@ -83,3 +84,4 @@ void fshader(float2 l_texcoordNW : TEXCOORD0, o_color = (colorNW + colorNE + colorSW + colorSE) * 0.25; } +""" diff --git a/direct/src/filter/filter-bloomx.sha b/direct/src/filter/filterBloomX.py similarity index 99% rename from direct/src/filter/filter-bloomx.sha rename to direct/src/filter/filterBloomX.py index 6819936fc4..38bc368101 100644 --- a/direct/src/filter/filter-bloomx.sha +++ b/direct/src/filter/filterBloomX.py @@ -1,3 +1,4 @@ +BLOOM_X = """ //Cg void vshader(float4 vtx_position : POSITION, @@ -45,3 +46,4 @@ void fshader(float4 l_texcoord0 : TEXCOORD0, color += 50 * tex2D(k_src, l_texcoord2.zw); o_color = color / 1200.0; } +""" diff --git a/direct/src/filter/filter-bloomy.sha b/direct/src/filter/filterBloomY.py similarity index 98% rename from direct/src/filter/filter-bloomy.sha rename to direct/src/filter/filterBloomY.py index 2351c70dd3..19ba36ae29 100644 --- a/direct/src/filter/filter-bloomy.sha +++ b/direct/src/filter/filterBloomY.py @@ -1,3 +1,4 @@ +BLOOM_Y = """ //Cg void vshader(float4 vtx_position : POSITION, @@ -36,3 +37,4 @@ void fshader(float4 l_texcoord0 : TEXCOORD0, o_color = color / 1200.0; o_color = o_color * k_intensity; } +""" diff --git a/direct/src/filter/filter-blurx.sha b/direct/src/filter/filterBlurX.py similarity index 94% rename from direct/src/filter/filter-blurx.sha rename to direct/src/filter/filterBlurX.py index 2cb6c1512d..6517b3ad55 100644 --- a/direct/src/filter/filter-blurx.sha +++ b/direct/src/filter/filterBlurX.py @@ -1,8 +1,9 @@ +BLUR_X = """ //Cg // //Cg profile arbvp1 arbfp1 -void vshader(float4 vtx_position : POSITION, +void vshader(float4 vtx_position : POSITION, float2 vtx_texcoord0 : TEXCOORD0, out float4 l_position : POSITION, out float2 l_texcoord0 : TEXCOORD0, @@ -31,3 +32,4 @@ void fshader(float2 l_texcoord0 : TEXCOORD0, o_color.w = 1; } +""" diff --git a/direct/src/filter/filter-blury.sha b/direct/src/filter/filterBlurY.py similarity index 94% rename from direct/src/filter/filter-blury.sha rename to direct/src/filter/filterBlurY.py index b927cf0329..abbf1d09ee 100644 --- a/direct/src/filter/filter-blury.sha +++ b/direct/src/filter/filterBlurY.py @@ -1,8 +1,9 @@ +BLUR_Y = """ //Cg // //Cg profile arbvp1 arbfp1 -void vshader(float4 vtx_position : POSITION, +void vshader(float4 vtx_position : POSITION, float2 vtx_texcoord0 : TEXCOORD0, out float4 l_position : POSITION, out float2 l_texcoord0 : TEXCOORD0, @@ -30,4 +31,4 @@ void fshader(float2 l_texcoord0 : TEXCOORD0, o_color /= 7; o_color.w = 1; } - +""" diff --git a/direct/src/filter/filter-copy.sha b/direct/src/filter/filterCopy.py similarity index 97% rename from direct/src/filter/filter-copy.sha rename to direct/src/filter/filterCopy.py index ce7f867554..d9770fb04b 100644 --- a/direct/src/filter/filter-copy.sha +++ b/direct/src/filter/filterCopy.py @@ -1,3 +1,4 @@ +COPY = """ //Cg @@ -17,4 +18,4 @@ void fshader(float2 l_texcoord : TEXCOORD0, { o_color = tex2D(k_src, l_texcoord); } - +""" diff --git a/direct/src/filter/filter-down4.sha b/direct/src/filter/filterDown4.py similarity index 98% rename from direct/src/filter/filter-down4.sha rename to direct/src/filter/filterDown4.py index 54d2967262..14595dbbd7 100644 --- a/direct/src/filter/filter-down4.sha +++ b/direct/src/filter/filterDown4.py @@ -1,3 +1,4 @@ +DOWN_4 = """ //Cg void vshader(float4 vtx_position : POSITION, @@ -30,4 +31,4 @@ void fshader(float2 l_texcoordNW : TEXCOORD0, float4 colorSE = tex2D(k_src, l_texcoordSE); o_color = (colorNW + colorNE + colorSW + colorSE); } - +""" From 153c277edde294dfb386293b3235cb371560a7c1 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 12 Jun 2017 22:52:05 +0200 Subject: [PATCH 125/424] Remove references to .sha filters in direct/src/filter --- direct/src/p3d/panda3d.pdef | 4 ---- makepanda/installer.nsi | 2 -- 2 files changed, 6 deletions(-) diff --git a/direct/src/p3d/panda3d.pdef b/direct/src/p3d/panda3d.pdef index 860c0b4671..78e7af2cf0 100644 --- a/direct/src/p3d/panda3d.pdef +++ b/direct/src/p3d/panda3d.pdef @@ -71,10 +71,6 @@ class panda3d(package): if sys.version_info < (3, 0): module('encodings.string_escape') - # Pick up the shader files that appear in direct/src/filter. - import direct - file(Filename(direct.__path__[0], 'filter/*.sha'), newDir = 'direct/filter') - # pandac.PandaModules pulls in other Panda3D libs automatically. # Exclude these Panda3D libs; they are big and many applications don't # use them. We define them as separate, optional packages, below, diff --git a/makepanda/installer.nsi b/makepanda/installer.nsi index c5e76fc766..8cfe6eba9a 100755 --- a/makepanda/installer.nsi +++ b/makepanda/installer.nsi @@ -318,8 +318,6 @@ SectionGroup "Python support" SetOutPath $INSTDIR\direct\directscripts File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\directscripts\*" - SetOutPath $INSTDIR\direct\filter - File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\filter\*.sha" SetOutPath $INSTDIR\direct File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\*.py" From 208cb18c4c0244cf966ca2f98bdb8a2fd539ff32 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 1 Jul 2017 15:48:38 -0700 Subject: [PATCH 126/424] deploy-ng: Fix pathing issues when deploying from Windows --- direct/src/showutil/dist.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 344ad7655b..4c2c42c0ba 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -209,7 +209,7 @@ class build_apps(distutils.core.Command): else: # Builtin module, but might not be builtin in wheel libs, so double check if module in whl_modules: - source_path = '{0}/deploy_libs/{1}.{2}'.format(p3dwhlfn, module, whl_modules_ext) + source_path = os.path.join(p3dwhlfn, 'deploy_libs/{}.{}'.format(module, whl_modules_ext)) basename = os.path.basename(source_path) else: continue @@ -339,12 +339,15 @@ class build_apps(distutils.core.Command): self.copy_with_dependencies(source_path, target_path, search_path) return - elif '.whl/' in source_path: + elif '.whl' in source_path: # Check whether the file exists inside the wheel. - whl, wf = source_path.split('.whl/') + whl, wf = source_path.split('.whl' + os.path.sep) whl += '.whl' whlfile = self._get_zip_file(whl) + # Normalize the path separator + wf = wf.replace(os.path.sep, '/') + # Look case-insensitively. namelist = whlfile.namelist() namelist_lower = [file.lower() for file in namelist] @@ -352,7 +355,7 @@ class build_apps(distutils.core.Command): if wf.lower() in namelist_lower: # We have a match. Change it to the correct case. wf = namelist[namelist_lower.index(wf.lower())] - source_path = '/'.join((whl, wf)) + source_path = os.path.join(whl, wf) target_path = os.path.join(target_dir, os.path.basename(wf)) self.copy_with_dependencies(source_path, target_path, search_path) return @@ -382,12 +385,16 @@ class build_apps(distutils.core.Command): source_path may be located inside a .whl file. """ - print("copying {0} -> {1}".format(os.path.relpath(source_path, self.build_base), os.path.relpath(target_path, self.build_base))) + try: + print("copying {0} -> {1}".format(os.path.relpath(source_path, self.build_base), os.path.relpath(target_path, self.build_base))) + except ValueError: + # No relative path (e.g., files on different drives in Windows), just print absolute paths instead + print("copying {0} -> {1}".format(source_path, target_path)) # Copy the file, and open it for analysis. - if '.whl/' in source_path: + if '.whl' in source_path: # This was found in a wheel, extract it - whl, wf = source_path.split('.whl/') + whl, wf = source_path.split('.whl' + os.path.sep) whl += '.whl' whlfile = self._get_zip_file(whl) data = whlfile.read(wf) From 624f28f0d5472a28cd8341f694ad3602a1de77d4 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 3 Jul 2017 14:45:33 +0200 Subject: [PATCH 127/424] direct: remove snake_case alias for CommonFilters.loadShader See #150 and #147. Fixes Travis build. --- direct/src/filter/CommonFilters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/direct/src/filter/CommonFilters.py b/direct/src/filter/CommonFilters.py index 9f49069a23..1d49b065d4 100644 --- a/direct/src/filter/CommonFilters.py +++ b/direct/src/filter/CommonFilters.py @@ -553,7 +553,6 @@ class CommonFilters: set_cartoon_ink = setCartoonInk del_bloom = delBloom del_ambient_occlusion = delAmbientOcclusion - load_shader = loadShader set_blur_sharpen = setBlurSharpen del_blur_sharpen = delBlurSharpen del_volumetric_lighting = delVolumetricLighting From d2d76c8a7525c37e60fe5a313dee2bba2c72f44f Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 5 Jul 2017 19:32:00 -0700 Subject: [PATCH 128/424] deploy-ng: Fix finding pyd files in whl files on Windows --- direct/src/showutil/FreezeTool.py | 6 +++--- direct/src/showutil/dist.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index d9f63d260b..e32f0abdf8 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1760,7 +1760,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): return None try: - fp = zip.open(fn, 'r') + fp = zip.open(fn.replace(os.path.sep, '/'), 'r') except KeyError: return None @@ -1803,13 +1803,13 @@ class PandaModuleFinder(modulefinder.ModuleFinder): # Look for recognized extensions. for stuff in self.suffixes: - suffix, mode, type = stuff + suffix, mode, _ = stuff fp = self._open_file(basename + suffix, mode) if fp: return (fp, basename + suffix, stuff) # Consider a package, i.e. a directory containing __init__.py. - for suffix, mode, type in self.suffixes: + for suffix, mode, _ in self.suffixes: init = os.path.join(basename, '__init__' + suffix) if self._open_file(init, mode): return (None, basename, ('', '', imp.PKG_DIRECTORY)) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 4c2c42c0ba..a6c6742dae 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -209,7 +209,7 @@ class build_apps(distutils.core.Command): else: # Builtin module, but might not be builtin in wheel libs, so double check if module in whl_modules: - source_path = os.path.join(p3dwhlfn, 'deploy_libs/{}.{}'.format(module, whl_modules_ext)) + source_path = os.path.join(p3dwhlfn, 'deploy_libs/{}.{}'.format(module, whl_modules_ext))#'{0}/deploy_libs/{1}.{2}'.format(p3dwhlfn, module, whl_modules_ext) basename = os.path.basename(source_path) else: continue From 09eb22b61e5ebed508a874eb90f19fef5df35261 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 6 Jul 2017 19:33:16 -0700 Subject: [PATCH 129/424] deploy-ng: Add plugins option to specify which plugins to copy NOTE: pandagl is no longer automatically copied and a graphics plugin must be specified. TODO: Per-platform overrides --- direct/src/showutil/dist.py | 5 ++++- samples/asteroids/setup.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index a6c6742dae..a2c9e869de 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -34,6 +34,7 @@ class build_apps(distutils.core.Command): self.include_modules = {} self.exclude_modules = {} self.deploy_platforms = [] + self.plugins = [] self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] self.build_scripts= { @@ -186,8 +187,10 @@ class build_apps(distutils.core.Command): freezer_extras.add((mod, None)) #FIXME: this is a temporary hack to pick up libpandagl. + plugin_list = ['panda3d/lib{}'.format(i) for i in self.plugins] for lib in p3dwhl.namelist(): - if lib.startswith('panda3d/libpandagl.'): + plugname = os.path.splitext(lib)[0] + if plugname in plugin_list: source_path = os.path.join(p3dwhlfn, lib) target_path = os.path.join(builddir, os.path.basename(lib)) search_path = [os.path.dirname(source_path)] diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 7ee64d2b0b..973f12b381 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -9,6 +9,9 @@ setup( 'gui_apps': { 'asteroids': 'main.py', }, + 'plugins': [ + 'pandagl', + ], 'deploy_platforms': [ 'manylinux1_x86_64', 'macosx_10_6_x86_64', From 14fa7c18cca1d79840adc72a169cc4af2246977c Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 6 Jul 2017 19:39:06 -0700 Subject: [PATCH 130/424] deploy-ng: Fix so whole directories can be skipped instead of skipping each file in a directory --- direct/src/showutil/dist.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index a2c9e869de..2f62ee3341 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -271,7 +271,7 @@ class build_apps(distutils.core.Command): def dir_has_files(directory): files = [ i for i in os.listdir(directory) - if not check_pattern(os.path.join(directory, i)) + if check_pattern(os.path.join(directory, i)) ] return bool(files) @@ -308,7 +308,9 @@ class build_apps(distutils.core.Command): d = os.path.join(dst, item) if os.path.isfile(s): copy_file(s, d) - elif dir_has_files(s): + elif not dir_has_files(s): + print("skipping directory", os.path.normpath(s)) + else: copy_dir(s, d) for path in self.copy_paths: From 894fe56d57681fe1dca7031be2395ef5f482772e Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 26 Jul 2017 21:06:48 -0700 Subject: [PATCH 131/424] deploy-ng: Fix for 14fa7c --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2f62ee3341..dfae276317 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -271,7 +271,7 @@ class build_apps(distutils.core.Command): def dir_has_files(directory): files = [ i for i in os.listdir(directory) - if check_pattern(os.path.join(directory, i)) + if not check_pattern(os.path.join(directory, i)) ] return bool(files) From dcef0c3c5d08f2a21370ea3ec5518a6b08b0246b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 19 Aug 2017 21:36:06 -0700 Subject: [PATCH 132/424] deploy-ng: Fix for finding Python extension modules when not running the frozen binary in the binary's directory --- direct/src/showutil/FreezeTool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index e32f0abdf8..95133008f8 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1658,7 +1658,8 @@ class Freezer: # trouble importing it as a builtin module. Synthesize a frozen # module that loads it dynamically. if '.' in moduleName: - code = compile('import sys;del sys.modules["%s"];import imp;imp.load_dynamic("%s", "%s%s")' % (moduleName, moduleName, moduleName, modext), moduleName, 'exec') + code = 'import sys;import os;del sys.modules["%s"];import imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) + code = compile(code, moduleName, 'exec') code = marshal.dumps(code) moduleList.append(make_module_list_entry(code, codeOffset, moduleName, module)) moduleBlob += code From 42d5874255022bb5eb6ded787948c0db8dba5580 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 6 Sep 2017 13:17:21 +0200 Subject: [PATCH 133/424] deploy-ng: fix cross-building for Windows and Python 2 --- direct/src/showutil/FreezeTool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 95133008f8..7494e682e6 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -742,7 +742,7 @@ class Freezer: self.moduleSuffixes += [ ('.cp{0}{1}-win_amd64.pyd'.format(*sys.version_info), 'rb', 3), ('.cp{0}{1}-win32.pyd'.format(*sys.version_info), 'rb', 3), - ('.dll', 'rb', 3), + ('.pyd', 'rb', 3), ] elif 'mac' in self.platform: self.moduleSuffixes += [ @@ -1658,7 +1658,7 @@ class Freezer: # trouble importing it as a builtin module. Synthesize a frozen # module that loads it dynamically. if '.' in moduleName: - code = 'import sys;import os;del sys.modules["%s"];import imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) + code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) code = compile(code, moduleName, 'exec') code = marshal.dumps(code) moduleList.append(make_module_list_entry(code, codeOffset, moduleName, module)) From 1c018f5bdbae1bfac777a32227538e00d1b6aa08 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 19 Sep 2017 22:03:44 +0200 Subject: [PATCH 134/424] deploy-ng: fixes for FreeBSD --- direct/src/showutil/FreezeTool.py | 9 ++++++--- direct/src/showutil/dist.py | 2 +- pandatool/src/deploy-stub/deploy-stub.c | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 7494e682e6..bda3c53d55 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -750,9 +750,12 @@ class Freezer: ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3), ('.so', 'rb', 3), ] - else: - print("Unknown platform %s" % (self.platform)) - self.moduleSuffixes = imp.get_suffixes() + else: # FreeBSD et al. + self.moduleSuffixes += [ + ('.cpython-{0}{1}m.so'.format(*sys.version_info), 'rb', 3), + ('.abi{0}.so'.format(*sys.version_info), 'rb', 3), + ('.so', 'rb', 3), + ] def excludeFrom(self, freezer): """ Excludes all modules that have already been processed by diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index dfae276317..61e96a947a 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -78,7 +78,7 @@ class build_apps(distutils.core.Command): whldir = os.path.join(self.build_base, '__whl_cache__') abi_tag = pip.pep425tags.get_abi_tag() - if 'u' in abi_tag and not platform.startswith('manylinux'): + if 'u' in abi_tag and (platform.startswith('win') or platform.startswith('macosx')): abi_tag = abi_tag.replace('u', '') pip_args = [ diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 3e066fe762..c46508fff6 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -3,6 +3,9 @@ #include "Python.h" #ifdef _WIN32 #include "malloc.h" + +#ifdef __FreeBSD__ +#include #endif #include @@ -165,7 +168,18 @@ int main(int argc, char *argv[]) { wchar_t buffer[2048]; GetModuleFileNameW(NULL, buffer, 2048); runtime = _wfopen(buffer, L"rb"); +#elif defined(__FreeBSD__) + size_t bufsize = 4096; + char buffer[4096]; + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + mib[3] = getpid(); + if (sysctl(mib, 4, (void *)buffer, &bufsize, NULL, 0) == -1) { + perror("sysctl"); + return 1; + } + runtime = fopen(buffer, "rb"); #else + // Let's hope that it was invoked with the executable name as first arg. runtime = fopen(argv[0], "rb"); #endif From 2026879ac903cff2b28db9b5a20813b5097fef3b Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 19 Sep 2017 22:08:41 +0200 Subject: [PATCH 135/424] deploy-ng: change format of blob to be easily mappable by deploy-ng --- direct/src/showutil/FreezeTool.py | 119 +++++++++++++++--------- pandatool/src/deploy-stub/deploy-stub.c | 92 ++++++++---------- 2 files changed, 113 insertions(+), 98 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index bda3c53d55..38c50fa11b 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1588,32 +1588,6 @@ class Freezer: return target def generateRuntimeFromStub(self, basename, stub_file): - def make_module_list_entry(code, offset, modulename, module): - size = len(code) - if getattr(module, "__path__", None): - # Indicate package by negative size - size = -size - modname = modulename.encode('ascii') - modnamelen = len(modulename) - return struct.pack( - ' +#endif #ifdef __FreeBSD__ #include #endif #include +#include #if PY_MAJOR_VERSION >= 3 #include @@ -34,8 +38,6 @@ static struct _inittab extensions[] = { #endif #endif -static unsigned char *modblob = NULL; - /* Main program */ #ifdef WIN_UNICODE @@ -159,8 +161,8 @@ int wmain(int argc, wchar_t *argv[]) { #else int main(int argc, char *argv[]) { #endif - struct _frozen *_PyImport_FrozenModules; - unsigned int listoff, modsoff, fsize, modsize, listsize, nummods, modidx; + struct _frozen *blob, *moddef; + uint64_t begin, end, size; int retval; FILE *runtime; @@ -184,66 +186,46 @@ int main(int argc, char *argv[]) { #endif // Get offsets - fseek(runtime, -12, SEEK_END); - fsize = ftell(runtime); - fread(&listoff, 4, 1, runtime); - fread(&modsoff, 4, 1, runtime); - fread(&nummods, 4, 1, runtime); - modsize = fsize - modsoff; - listsize = modsoff - listoff; + fseek(runtime, -8, SEEK_END); + end = ftell(runtime); + fread(&begin, 8, 1, runtime); + size = end - begin; - // Read module blob - modblob = malloc(modsize); - fseek(runtime, modsoff, SEEK_SET); - fread(modblob, modsize, 1, runtime); - - // Read module list - _PyImport_FrozenModules = calloc(nummods + 1, sizeof(struct _frozen)); - fseek(runtime, listoff, SEEK_SET); - for (modidx = 0; modidx < nummods; ++modidx) { - struct _frozen *moddef = &_PyImport_FrozenModules[modidx]; - unsigned char name_size; - char *name = NULL, namebuf[256] = {0}; - size_t nsize; - unsigned int codeptr; - int codesize; - - // Name - fread(&name_size, 1, 1, runtime); - fread(namebuf, 1, name_size, runtime); - nsize = strlen(namebuf) + 1; - name = malloc(nsize); - memcpy(name, namebuf, nsize); - moddef->name = name; - - // Pointer - fread(&codeptr, 4, 1, runtime); - moddef->code = modblob + codeptr; - - // Size - fread(&codesize, 4, 1, runtime); - moddef->size = codesize; - } + // mmap the section indicated by the offset (or malloc/fread on windows) +#ifdef _WIN32 + blob = (struct _frozen *)malloc(size); + assert(blob != NULL); + fseek(runtime, (long)begin, SEEK_SET); + fread(blob, size, 1, runtime); +#else + blob = (struct _frozen *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), begin); + assert(blob != NULL); +#endif fclose(runtime); - // Uncomment this to print out the read in module list - //for (modidx = 0; modidx < nummods; ++modidx) { - // struct _frozen *moddef = &_PyImport_FrozenModules[modidx]; - // printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size); - //} + // Offset the pointers in the table using the base mmap address. + moddef = blob; + while (moddef->name) { + moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob); + if (moddef->code != 0) { + moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob); + } + //printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size); + moddef++; + } // Run frozen application - PyImport_FrozenModules = _PyImport_FrozenModules; + PyImport_FrozenModules = blob; retval = Py_FrozenMain(argc, argv); // Free resources - free(modblob); - for (modidx = 0; modidx < nummods; ++modidx) { - struct _frozen *moddef = &_PyImport_FrozenModules[modidx]; - free((void*)moddef->name); - } - free(_PyImport_FrozenModules); +#ifdef _WIN32 + free(blob); +#else + munmap(blob, size); +#endif + return retval; } #ifdef WIN_UNICODE From 4dfa70115e784c12a3a599d195f0541029204354 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 19 Sep 2017 23:23:57 +0200 Subject: [PATCH 136/424] deploy-ng: fix issues locating certain files inside .whl --- direct/src/showutil/dist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 61e96a947a..81b40a8b5a 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -189,7 +189,7 @@ class build_apps(distutils.core.Command): #FIXME: this is a temporary hack to pick up libpandagl. plugin_list = ['panda3d/lib{}'.format(i) for i in self.plugins] for lib in p3dwhl.namelist(): - plugname = os.path.splitext(lib)[0] + plugname = lib.split('.', 1)[0] if plugname in plugin_list: source_path = os.path.join(p3dwhlfn, lib) target_path = os.path.join(builddir, os.path.basename(lib)) @@ -402,7 +402,7 @@ class build_apps(distutils.core.Command): whl, wf = source_path.split('.whl' + os.path.sep) whl += '.whl' whlfile = self._get_zip_file(whl) - data = whlfile.read(wf) + data = whlfile.read(wf.replace(os.path.sep, '/')) with open(target_path, 'wb') as f: f.write(data) # Wrap the data in a BytesIO, since we need to be able to seek in From 37b9e256d829f423f765aee3cc3c983260b2bd07 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 19 Nov 2017 15:30:45 -0800 Subject: [PATCH 137/424] deploy-ng: Raise a RuntimeError if pip is not version 9.0 or greater This is when pip download was introduced and is required for deploy-ng. --- direct/src/showutil/dist.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 81b40a8b5a..2ff22f07ca 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -81,6 +81,10 @@ class build_apps(distutils.core.Command): if 'u' in abi_tag and (platform.startswith('win') or platform.startswith('macosx')): abi_tag = abi_tag.replace('u', '') + pip_version = pip.__version__.split('.') + if int(pip_version[0]) < 9: + raise RuntimeError("pip 9.0 or greater is required, but found {}".format(pip.__version__)) + pip_args = [ 'download', '-d', whldir, From 3995ea4c3b14df84f4175b4bdcb7b163a9d851ff Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 21 Nov 2017 11:07:39 -0800 Subject: [PATCH 138/424] deploy-ng: Fix skipping ignored directories --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2ff22f07ca..7a43982a22 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -275,7 +275,7 @@ class build_apps(distutils.core.Command): def dir_has_files(directory): files = [ i for i in os.listdir(directory) - if not check_pattern(os.path.join(directory, i)) + if not check_pattern(os.path.normpath(os.path.join(directory, i))) ] return bool(files) From 54dad81646a10391c076d7346278735f41948c3f Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 21 Nov 2017 14:57:15 -0800 Subject: [PATCH 139/424] deploy-ng: Add RPATH to search_path when parsing ELF files --- direct/src/showutil/dist.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 7a43982a22..a480ccb871 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -428,7 +428,7 @@ class build_apps(distutils.core.Command): elif magic == b'\x7FELF': # Elf magic. Used on (among others) Linux and FreeBSD. - deps = self._read_dependencies_elf(fp) + deps = self._read_dependencies_elf(fp, os.path.dirname(source_path), search_path) elif magic in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE', b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'): @@ -448,7 +448,7 @@ class build_apps(distutils.core.Command): for dep in deps: self.add_dependency(dep, target_dir, search_path, base) - def _read_dependencies_elf(self, elf): + def _read_dependencies_elf(self, elf, origin, search_path): """ Having read the first 4 bytes of the ELF file, fetches the dependent libraries and returns those as a list. """ @@ -499,13 +499,16 @@ class build_apps(distutils.core.Command): elif tag == 15 or tag == 29: # An RPATH or RUNPATH entry. string = string_tables[link][val : string_tables[link].find(b'\0', val)] - rpath += string.split(b':') + rpath += [ + os.path.normpath(i.decode('utf-8').replace('$ORIGIN', origin)) + for i in string.split(b':') + ] data = elf.read(entsize) tag, val = struct.unpack_from(dynamic_struct, data) elf.close() - #TODO: should we respect the RPATH? Clear it? Warn about it? + search_path += rpath return needed def _read_dependencies_macho(self, fp): From 44a4d18a8ea0f0946e6c9f1da3736933c6c93e70 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 21 Nov 2017 18:37:17 -0800 Subject: [PATCH 140/424] deploy-ng: Add descriptions to build_apps and bdist_apps setuptools commands --- direct/src/showutil/dist.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index a480ccb871..fa94729fd6 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -23,6 +23,7 @@ if 'basestring' not in globals(): class build_apps(distutils.core.Command): + description = 'build Panda3D applications' user_options = [] # TODO def initialize_options(self): @@ -555,6 +556,7 @@ class build_apps(distutils.core.Command): class bdist_apps(distutils.core.Command): + description = 'bundle built Panda3D applications into distributable forms' user_options = [] def initialize_options(self): From 56ebf981df7be644b0ca8a5b82329441d0b46558 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 21 Nov 2017 18:48:42 -0800 Subject: [PATCH 141/424] deploy-ng: Add user_options and finalize_options to build_apps --- direct/src/showutil/dist.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index fa94729fd6..c8c6a8d4d4 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -12,6 +12,7 @@ import io import distutils.core import distutils.dir_util import distutils.file_util +import distutils.log from . import FreezeTool from . import pefile @@ -24,7 +25,10 @@ if 'basestring' not in globals(): class build_apps(distutils.core.Command): description = 'build Panda3D applications' - user_options = [] # TODO + user_options = [ + ('build-base', None, 'directory to build applications in'), + ('requirements-path', None, 'path to requirements.txt file for pip'), + ] def initialize_options(self): self.build_base = os.path.join(os.getcwd(), 'build') @@ -55,8 +59,14 @@ class build_apps(distutils.core.Command): return zip def finalize_options(self): - # TODO - pass + for path in self.copy_paths: + if isinstance(path, basestring): + src = path + else: + src, _ = path + assert os.path.exists(src), 'Copy path source does not exist: {}'.format(src) + assert self.deploy_platforms, 'At least one deploy platform must be defined' + assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) def run(self): if not self.deploy_platforms: From a2fd511db8e6a3cbd7b72a4a48510631d4583e6b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 21 Nov 2017 19:02:27 -0800 Subject: [PATCH 142/424] deploy-ng: Make use of distutils.core.Command.announce() instead of print() for verbosity levels --- direct/src/showutil/dist.py | 39 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index c8c6a8d4d4..3cbda27090 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -6,12 +6,11 @@ import pip import sys import subprocess import zipfile +import shutil import struct import io import distutils.core -import distutils.dir_util -import distutils.file_util import distutils.log from . import FreezeTool @@ -75,7 +74,7 @@ class build_apps(distutils.core.Command): else: platforms = self.deploy_platforms use_wheels = True - print("Building platforms: {0}".format(','.join(platforms))) + self.announce('Building platforms: {0}'.format(','.join(platforms)), distutils.log.INFO) for platform in platforms: self.build_runtimes(platform, use_wheels) @@ -119,8 +118,8 @@ class build_apps(distutils.core.Command): builddir = os.path.join(self.build_base, platform) if os.path.exists(builddir): - distutils.dir_util.remove_tree(builddir) - distutils.dir_util.mkpath(builddir) + shutil.rmtree(builddir) + os.mkdir(builddir) path = sys.path[:] p3dwhl = None @@ -247,9 +246,9 @@ class build_apps(distutils.core.Command): dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) libdir = os.path.dirname(dtool_fn.to_os_specific()) src = os.path.join(libdir, '..', 'etc') - distutils.dir_util.copy_tree(src, etcdir) + shutil.copytree(src, etcdir) else: - distutils.dir_util.mkpath(etcdir) + os.makedirs(etcdir) # Combine prc files with libs and copy the whole list panda_files = [i for i in p3dwhl.namelist() if i.endswith('.prc')] @@ -278,7 +277,7 @@ class build_apps(distutils.core.Command): for pattern in ignore_copy_list: # Normalize file paths across platforms path = p3d.Filename.from_os_specific(src).get_fullpath() - #print("check ignore:", pattern, src, pattern.matches(path)) + #self.announce('check ignore: {} {} {}'.format(pattern, src, pattern.matches(path))) if pattern.matches(path): return True return False @@ -295,12 +294,12 @@ class build_apps(distutils.core.Command): dst = os.path.normpath(dst) if check_pattern(src): - print("skipping file", src) + self.announce('skipping file {}'.format(src)) return dst_dir = os.path.dirname(dst) if not os.path.exists(dst_dir): - distutils.dir_util.mkpath(dst_dir) + os.makedirs(dst_dir) ext = os.path.splitext(src)[1] if not ext: @@ -311,11 +310,11 @@ class build_apps(distutils.core.Command): dst_ext, script = self.build_scripts[ext] dst = dst_root + dst_ext script = script.format(src, dst) - print("using script:", script) + self.announce('using script: {}'.format(script)) subprocess.call(script.split()) else: - #print("Copy file", src, dst) - distutils.file_util.copy_file(src, dst) + self.announce('copying {0} -> {1}'.format(src, dst)) + shutil.copyfile(src, dst) def copy_dir(src, dst): for item in os.listdir(src): @@ -324,7 +323,7 @@ class build_apps(distutils.core.Command): if os.path.isfile(s): copy_file(s, d) elif not dir_has_files(s): - print("skipping directory", os.path.normpath(s)) + self.announce('skipping directory'.format(os.path.normpath(s))) else: copy_dir(s, d) @@ -406,10 +405,10 @@ class build_apps(distutils.core.Command): source_path may be located inside a .whl file. """ try: - print("copying {0} -> {1}".format(os.path.relpath(source_path, self.build_base), os.path.relpath(target_path, self.build_base))) + self.announce('copying {0} -> {1}'.format(os.path.relpath(source_path, self.build_base), os.path.relpath(target_path, self.build_base))) except ValueError: # No relative path (e.g., files on different drives in Windows), just print absolute paths instead - print("copying {0} -> {1}".format(source_path, target_path)) + self.announce('copying {0} -> {1}'.format(source_path, target_path)) # Copy the file, and open it for analysis. if '.whl' in source_path: @@ -425,7 +424,7 @@ class build_apps(distutils.core.Command): fp = io.BytesIO(data) else: # Regular file, copy it - distutils.file_util.copy_file(source_path, target_path) + shutil.copy_file(source_path, target_path) fp = open(target_path, 'rb') # What kind of magic does the file contain? @@ -594,12 +593,12 @@ class bdist_apps(distutils.core.Command): basename = '{}_{}'.format(self.distribution.get_fullname(), platform) if (os.path.exists(temp_dir)): - distutils.dir_util.remove_tree(temp_dir) - distutils.dir_util.copy_tree(build_dir, temp_dir) + shutil.rmtree(temp_dir) + shutil.copy_tree(build_dir, temp_dir) distutils.archive_util.make_archive(basename, archive_format, root_dir=build_base, base_dir=base_dir) - distutils.dir_util.remove_tree(temp_dir) + shutil.rmtree(temp_dir) def setup(**attrs): commandClasses = attrs.setdefault("cmdclass", {}) From 37572b312c9fb4b6066d9bc6e03b48d9192e1aad Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 22 Nov 2017 19:27:14 +0100 Subject: [PATCH 143/424] loader: try appending .bam to filename if loading doesn't find model --- panda/src/pgraph/loader.cxx | 141 +++++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 51 deletions(-) diff --git a/panda/src/pgraph/loader.cxx b/panda/src/pgraph/loader.cxx index 32187508e0..ae0bd7b01d 100644 --- a/panda/src/pgraph/loader.cxx +++ b/panda/src/pgraph/loader.cxx @@ -159,36 +159,29 @@ load_file(const Filename &filename, const LoaderOptions &options) const { "in your Config.prc to globally assume a particular model " "filename extension.\n"; } - return NULL; + return nullptr; } LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr(); - LoaderFileType *requested_type = - reg->get_type_from_extension(extension); - if (requested_type == (LoaderFileType *)NULL) { - if (report_errors) { - loader_cat.error() - << "Extension of file " << this_filename - << " is unrecognized; cannot load.\n"; - loader_cat.error(false) - << "Currently known scene file types are:\n"; - reg->write(loader_cat.error(false), 2); + LoaderFileType *requested_type = reg->get_type_from_extension(extension); + // requested_type may be nullptr at this point, because there's still a + // chance we can load it via the model-cache or by adding .bam to the end. + if (requested_type != nullptr) { + if (!requested_type->supports_load()) { + if (report_errors) { + loader_cat.error() + << requested_type->get_name() << " file type (." + << extension << ") does not support loading.\n"; + } + return nullptr; + } else if (compressed && !requested_type->supports_compressed()) { + if (report_errors) { + loader_cat.error() + << requested_type->get_name() << " file type (." + << extension << ") does not support in-line compression.\n"; + } + return nullptr; } - return NULL; - } else if (!requested_type->supports_load()) { - if (report_errors) { - loader_cat.error() - << requested_type->get_name() << " file type (." - << extension << ") does not support loading.\n"; - } - return NULL; - } else if (compressed && !requested_type->supports_compressed()) { - if (report_errors) { - loader_cat.error() - << requested_type->get_name() << " file type (." - << extension << ") does not support in-line compression.\n"; - } - return NULL; } bool search = (this_options.get_flags() & LoaderOptions::LF_search) != 0; @@ -227,6 +220,14 @@ load_file(const Filename &filename, const LoaderOptions &options) const { } if (any_exist) { + if (requested_type == nullptr) { + loader_cat.error() + << "Extension of file " << this_filename + << " is unrecognized; cannot load.\n"; + loader_cat.error(false) + << "Currently known scene file types are:\n"; + reg->write(loader_cat.error(false), 2); + } loader_cat.error() << "Couldn't load file " << this_filename << ": all matching files on model path invalid " @@ -248,6 +249,14 @@ load_file(const Filename &filename, const LoaderOptions &options) const { } if (report_errors) { if (vfs->exists(this_filename)) { + if (requested_type == nullptr) { + loader_cat.error() + << "Extension of file " << this_filename + << " is unrecognized; cannot load.\n"; + loader_cat.error(false) + << "Currently known scene file types are:\n"; + reg->write(loader_cat.error(false), 2); + } loader_cat.error() << "Couldn't load file " << this_filename << ": invalid.\n"; } else { @@ -330,36 +339,66 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, } } - bool cache_only = (options.get_flags() & LoaderOptions::LF_cache_only) != 0; - if (!cache_only) { - // Load the model from disk. - PT(PandaNode) result = requested_type->load_file(pathname, options, record); - if (result != (PandaNode *)NULL) { - if (record != (BamCacheRecord *)NULL) { - // Store the loaded model in the model cache. - record->set_data(result); - cache->store(record); - } + if (options.get_flags() & LoaderOptions::LF_cache_only) { + // We're not allowed to read from disk. + return nullptr; + } - if (premunge_data) { - SceneGraphReducer sgr; - sgr.premunge(result, RenderState::make_empty()); - } + // Load the model from disk. + PT(PandaNode) result; + if (requested_type != nullptr) { + result = requested_type->load_file(pathname, options, record); + } + if (result != nullptr) { + if (record != nullptr) { + // Store the loaded model in the model cache. + record->set_data(result); + cache->store(record); + } + } else { + // Do we have the same filename, but with .bam appended to the end? + string extension = pathname.get_extension(); + Filename pathname_bam = pathname; + if (extension == "pz" || extension == "gz") { + // Strip .pz/.gz, so that model.egg.pz -> model.egg.bam + extension = pathname_bam.get_extension(); + pathname_bam = pathname_bam.get_fullpath_wo_extension(); + } + if (extension == "bam") { + // Don't try to load .bam.bam files, that is just silly. + return nullptr; + } + pathname_bam += ".bam"; - if (allow_ram_cache && result->is_of_type(ModelRoot::get_class_type())) { - // Store the loaded model in the RAM cache, and make sure we return a - // copy so that this node can be modified independently from the RAM - // cached version. - ModelPool::add_model(pathname, DCAST(ModelRoot, result.p())); - if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) { - result = result->copy_subgraph(); - } - } - return result; + BamFile bam_file; + if (!bam_file.open_read(pathname_bam, report_errors)) { + return nullptr; + } + + bam_file.get_reader()->set_loader_options(options); + result = bam_file.read_node(report_errors); + + nassertr_always(result != nullptr && result->is_of_type(ModelRoot::get_class_type()), nullptr); + // We don't bother with the model-cache here, since this .bam file is + // already effectively a cached version of the original model. + } + + if (premunge_data) { + SceneGraphReducer sgr; + sgr.premunge(result, RenderState::make_empty()); + } + + if (allow_ram_cache && result->is_of_type(ModelRoot::get_class_type())) { + // Store the loaded model in the RAM cache, and make sure we return a + // copy so that this node can be modified independently from the RAM + // cached version. + ModelPool::add_model(pathname, DCAST(ModelRoot, result.p())); + if ((options.get_flags() & LoaderOptions::LF_allow_instance) == 0) { + result = result->copy_subgraph(); } } - return NULL; + return result; } /** From 6ec4e5db68b91e5b2fa39b4672ee2f2f9bef00fe Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 22 Nov 2017 19:28:08 +0100 Subject: [PATCH 144/424] deploy-ng: fix typo in copyfile call --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 3cbda27090..d2c21ee0b2 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -424,7 +424,7 @@ class build_apps(distutils.core.Command): fp = io.BytesIO(data) else: # Regular file, copy it - shutil.copy_file(source_path, target_path) + shutil.copyfile(source_path, target_path) fp = open(target_path, 'rb') # What kind of magic does the file contain? From da2ad0f0bc499edc8f46fdc1c2ba937461aaeda6 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 22 Nov 2017 19:33:17 +0100 Subject: [PATCH 145/424] deploy-ng: default to ACP if console codepage codec wasn't frozen This is necessary because when Python is initialized, it takes the codec to use from GetConsoleCP() and GetConsoleOutputCP() without bothering to check whether the given codec is available. However, in most cases, the console codepage will be the same as the ANSI codepage (ie. GetACP()) which is always supported by Python via the 'mbcs' codec. So what we do is we check whether the console codepage is frozen in, and if not, we set the console codepage to the ANSI codepage and set the stdin/stdout/stderr encoding to 'mbcs'. This is still not a perfect solution because the ACP may not be able to encode all characters that the application is printing, which would still result in unexpected errors. Ideally, we'd pull in Python 3.6's _io._WindowsConsoleIO class, which bypasses this whole mess by directly using the wide-character Windows APIs to write to the console. --- direct/src/showutil/FreezeTool.py | 2 + pandatool/src/deploy-stub/deploy-stub.c | 66 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 38c50fa11b..a8a967f93a 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -30,6 +30,8 @@ isDebugBuild = (python.lower().endswith('_d')) # These are modules that Python always tries to import up-front. They # must be frozen in any main.exe. +# NB. if encodings are removed, be sure to remove them from the shortcut in +# deploy-stub.c. startupModules = [ 'encodings', 'encodings.aliases', 'encodings.undefined', 'encodings.ascii', 'encodings.cp1252', 'encodings.latin_1', 'encodings.utf_8', diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index d35d3f59a9..51d7da88a9 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -38,6 +38,32 @@ static struct _inittab extensions[] = { #endif #endif +#if defined(_WIN32) && PY_VERSION_HEX < 0x03060000 +static int supports_code_page(UINT cp) { + /* Shortcut, because we know that these encodings are bundled by default-- + * see FreezeTool.py and Python's encodings/aliases.py */ + if (cp != 0 && cp != 1252 && cp != 367 && cp != 437 && cp != 850 && cp != 819) { + const struct _frozen *moddef; + char codec[100]; + + /* Check if the codec was frozen into the program. We can't check this + * using _PyCodec_Lookup, since Python hasn't been initialized yet. */ + PyOS_snprintf(codec, sizeof(codec), "encodings.cp%u", (unsigned int)cp); + + moddef = PyImport_FrozenModules; + while (moddef->name) { + if (strcmp(moddef->name, codec) == 0) { + return 1; + } + ++moddef; + } + return 0; + } + + return 1; +} +#endif + /* Main program */ #ifdef WIN_UNICODE @@ -64,6 +90,19 @@ int Py_FrozenMain(int argc, char **argv) } #endif +#if defined(MS_WINDOWS) && PY_VERSION_HEX >= 0x03040000 && PY_VERSION_HEX < 0x03060000 + if (!supports_code_page(GetConsoleOutputCP()) || + !supports_code_page(GetConsoleCP())) { + /* Revert to the active codepage, and tell Python to use the 'mbcs' + * encoding (which always uses the active codepage). In 99% of cases, + * this will be the same thing anyway. */ + UINT acp = GetACP(); + SetConsoleCP(acp); + SetConsoleOutputCP(acp); + Py_SetStandardStreamEncoding("mbcs", NULL); + } +#endif + Py_FrozenFlag = 1; /* Suppress errors from getpath.c */ Py_NoSiteFlag = 1; Py_NoUserSiteDirectory = 1; @@ -112,6 +151,33 @@ int Py_FrozenMain(int argc, char **argv) PyWinFreeze_ExeInit(); #endif +#if defined(MS_WINDOWS) && PY_VERSION_HEX < 0x03040000 + if (!supports_code_page(GetConsoleOutputCP()) || + !supports_code_page(GetConsoleCP())) { + /* Same hack as before except for Python 2.7, which doesn't seem to have + * a way to set the encoding ahead of time, and setting PYTHONIOENCODING + * doesn't seem to work. Fortunately, Python 2.7 doesn't usually start + * causing codec errors until the first print statement. */ + PyObject *sys_stream; + UINT acp = GetACP(); + SetConsoleCP(acp); + SetConsoleOutputCP(acp); + + sys_stream = PySys_GetObject("stdin"); + if (sys_stream && PyFile_Check(sys_stream)) { + PyFile_SetEncodingAndErrors(sys_stream, "mbcs", NULL); + } + sys_stream = PySys_GetObject("stdout"); + if (sys_stream && PyFile_Check(sys_stream)) { + PyFile_SetEncodingAndErrors(sys_stream, "mbcs", NULL); + } + sys_stream = PySys_GetObject("stderr"); + if (sys_stream && PyFile_Check(sys_stream)) { + PyFile_SetEncodingAndErrors(sys_stream, "mbcs", NULL); + } + } +#endif + if (Py_VerboseFlag) fprintf(stderr, "Python %s\n%s\n", Py_GetVersion(), Py_GetCopyright()); From 80b6021d225c6e201fed576c578d38209bbcbc33 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 22 Nov 2017 20:05:50 +0100 Subject: [PATCH 146/424] deploy-ng: don't warn about missing Windows system libraries --- direct/src/showutil/dist.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index d2c21ee0b2..3d6ebdaa28 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -44,7 +44,14 @@ class build_apps(distutils.core.Command): self.build_scripts= { '.egg': ('.bam', 'egg2bam -o {1} {0}'), } - self.exclude_dependencies = [] + self.exclude_dependencies = [ + 'kernel32.dll', 'user32.dll', 'wsock32.dll', 'ws2_32.dll', + 'advapi32.dll', 'opengl32.dll', 'glu32.dll', 'gdi32.dll', + 'shell32.dll', 'ntdll.dll', 'ws2help.dll', 'rpcrt4.dll', + 'imm32.dll', 'ddraw.dll', 'shlwapi.dll', 'secur32.dll', + 'dciman32.dll', 'comdlg32.dll', 'comctl32.dll', 'ole32.dll', + 'oleaut32.dll', 'gdiplus.dll', 'winmm.dll', 'iphlpapi.dll', + 'msvcrt.dll', 'kernelbase.dll', 'msimg32.dll', 'msacm32.dll'] # We keep track of the zip files we've opened. self._zip_files = {} @@ -347,7 +354,7 @@ class build_apps(distutils.core.Command): # We've already added it earlier. return - if name in self.exclude_dependencies: + if name.lower() in self.exclude_dependencies: return for dir in search_path: @@ -395,7 +402,7 @@ class build_apps(distutils.core.Command): # Warn if we can't find it, but only once. self.warn("could not find dependency {0} (referenced by {1})".format(name, referenced_by)) - self.exclude_dependencies.append(name) + self.exclude_dependencies.append(name.lower()) def copy_with_dependencies(self, source_path, target_path, search_path): """ Copies source_path to target_path. It also scans source_path for @@ -434,7 +441,9 @@ class build_apps(distutils.core.Command): # It's a Windows DLL or EXE file. pe = pefile.PEFile() pe.read(fp) - deps = pe.imports + for lib in pe.imports: + if not lib.lower().startswith('api-ms-win-'): + deps.append(lib) elif magic == b'\x7FELF': # Elf magic. Used on (among others) Linux and FreeBSD. From 82ced9900e7cf531ba41c5f497239063f7804828 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 22 Nov 2017 20:57:03 +0100 Subject: [PATCH 147/424] deploy-ng: allow using setup.cfg instead of setup.py --- direct/src/showutil/dist.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 3d6ebdaa28..9ec6c0c84d 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -65,6 +65,36 @@ class build_apps(distutils.core.Command): return zip def finalize_options(self): + # We need to massage the inputs a bit in case they came from a + # setup.cfg file. + def _parse_list(input): + if isinstance(input, basestring): + input = input.strip().replace(',', '\n') + if input: + return [item.strip() for item in input.split('\n') if item.strip()] + else: + return [] + else: + return input + + def _parse_dict(input): + if isinstance(input, dict): + return input + d = {} + for item in _parse_list(input): + key, sep, value = item.partition('=') + d[key.strip()] = value.strip() + return d + + self.gui_apps = _parse_dict(self.gui_apps) + self.console_apps = _parse_dict(self.console_apps) + + self.copy_paths = _parse_list(self.copy_paths) + self.exclude_paths = _parse_list(self.exclude_paths) + self.exclude_modules = _parse_list(self.include_modules) + self.exclude_modules = _parse_list(self.exclude_modules) + self.plugins = _parse_list(self.plugins) + for path in self.copy_paths: if isinstance(path, basestring): src = path @@ -78,6 +108,9 @@ class build_apps(distutils.core.Command): if not self.deploy_platforms: platforms = [p3d.PandaSystem.get_platform()] use_wheels = False + elif isinstance(self.deploy_platforms, basestring): + platforms = [self.deploy_platforms] + use_wheels = True else: platforms = self.deploy_platforms use_wheels = True From 39dba025b750865615dd4632d0292f779af9f8ad Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 22 Nov 2017 21:42:17 +0100 Subject: [PATCH 148/424] deploy-ng: fix library suffix being stripped on FreeBSD --- direct/src/showutil/dist.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 9ec6c0c84d..54eba63fa0 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -261,8 +261,9 @@ class build_apps(distutils.core.Command): # Remove python version string if sys.version_info >= (3, 0): parts = basename.split('.') - parts = parts[:-2] + parts[-1:] - basename = '.'.join(parts) + if len(parts) >= 3 and '-' in parts[-2]: + parts = parts[:-2] + parts[-1:] + basename = '.'.join(parts) else: # Builtin module, but might not be builtin in wheel libs, so double check if module in whl_modules: From a8d1e9444bc64f20d669f96639ec22f21e80a0b6 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 22 Nov 2017 11:26:27 -0800 Subject: [PATCH 149/424] deploy-ng: Fix build_apps user options --- direct/src/showutil/dist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 54eba63fa0..ef2a4a5108 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -25,8 +25,8 @@ if 'basestring' not in globals(): class build_apps(distutils.core.Command): description = 'build Panda3D applications' user_options = [ - ('build-base', None, 'directory to build applications in'), - ('requirements-path', None, 'path to requirements.txt file for pip'), + ('build-base=', None, 'directory to build applications in'), + ('requirements-path=', None, 'path to requirements.txt file for pip'), ] def initialize_options(self): From 949214834c24ca25b0deaa5a2d1065c3871b858d Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 22 Nov 2017 14:02:30 -0800 Subject: [PATCH 150/424] deploy-ng: Fix error when build directory is missing --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index ef2a4a5108..47e4557d69 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -159,7 +159,7 @@ class build_apps(distutils.core.Command): if os.path.exists(builddir): shutil.rmtree(builddir) - os.mkdir(builddir) + os.makedirs(builddir) path = sys.path[:] p3dwhl = None From 214b67c3c034d6a1974ac7fabfb2a004b7af947c Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 22 Nov 2017 15:39:30 -0800 Subject: [PATCH 151/424] deploy-ng: Convert .egg to .egg.bam instead of .bam --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 47e4557d69..9bf2622ba5 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -42,7 +42,7 @@ class build_apps(distutils.core.Command): self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] self.build_scripts= { - '.egg': ('.bam', 'egg2bam -o {1} {0}'), + '.egg': ('.egg.bam', 'egg2bam -o {1} {0}'), } self.exclude_dependencies = [ 'kernel32.dll', 'user32.dll', 'wsock32.dll', 'ws2_32.dll', From bec9ff8a40584431ef072fc2bc073cd8c8710e71 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 23 Nov 2017 01:15:44 +0100 Subject: [PATCH 152/424] loader: fix UB in try_load_file when loading file without known type --- panda/src/pgraph/loader.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/panda/src/pgraph/loader.cxx b/panda/src/pgraph/loader.cxx index ae0bd7b01d..27b8404f04 100644 --- a/panda/src/pgraph/loader.cxx +++ b/panda/src/pgraph/loader.cxx @@ -277,7 +277,8 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, LoaderFileType *requested_type) const { BamCache *cache = BamCache::get_global_ptr(); - bool allow_ram_cache = requested_type->get_allow_ram_cache(options); + bool allow_ram_cache = + ((options.get_flags() & LoaderOptions::LF_no_ram_cache) == 0); if (allow_ram_cache) { // If we're allowing a RAM cache, use the ModelPool to load the file. @@ -298,7 +299,8 @@ try_load_file(const Filename &pathname, const LoaderOptions &options, bool report_errors = ((options.get_flags() & LoaderOptions::LF_report_errors) != 0 || loader_cat.is_debug()); PT(BamCacheRecord) record; - if (cache->get_cache_models() && requested_type->get_allow_disk_cache(options)) { + if (cache->get_cache_models() && + (options.get_flags() & LoaderOptions::LF_no_disk_cache) == 0) { // See if the model can be found in the on-disk cache, if it is active. record = cache->lookup(pathname, "bam"); if (record != (BamCacheRecord *)NULL) { From ffdac54a0bc6d417bcafc315d5c3cdbc84cb5ecf Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 22 Nov 2017 18:43:49 -0800 Subject: [PATCH 153/424] deploy-ng: Switch copy_paths to include_paths and rename_paths --- direct/src/showutil/dist.py | 62 +++++++++++++++++-------------------- samples/asteroids/setup.py | 8 +++-- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 9bf2622ba5..cb02baa2f3 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -34,6 +34,8 @@ class build_apps(distutils.core.Command): self.gui_apps = {} self.console_apps = {} self.copy_paths = [] + self.rename_paths = {} + self.include_paths = [] self.exclude_paths = [] self.include_modules = {} self.exclude_modules = {} @@ -89,9 +91,11 @@ class build_apps(distutils.core.Command): self.gui_apps = _parse_dict(self.gui_apps) self.console_apps = _parse_dict(self.console_apps) + self.rename_paths = _parse_dict(self.rename_paths) self.copy_paths = _parse_list(self.copy_paths) + self.include_paths = _parse_list(self.include_paths) self.exclude_paths = _parse_list(self.exclude_paths) - self.exclude_modules = _parse_list(self.include_modules) + self.include_modules = _parse_list(self.include_modules) self.exclude_modules = _parse_list(self.exclude_modules) self.plugins = _parse_list(self.plugins) @@ -240,7 +244,7 @@ class build_apps(distutils.core.Command): if mod in whl_modules: freezer_extras.add((mod, None)) - #FIXME: this is a temporary hack to pick up libpandagl. + # Copy over necessary plugins plugin_list = ['panda3d/lib{}'.format(i) for i in self.plugins] for lib in p3dwhl.namelist(): plugname = lib.split('.', 1)[0] @@ -314,27 +318,26 @@ class build_apps(distutils.core.Command): ignore_copy_list += self.exclude_paths ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] - def check_pattern(src): - for pattern in ignore_copy_list: - # Normalize file paths across platforms - path = p3d.Filename.from_os_specific(src).get_fullpath() - #self.announce('check ignore: {} {} {}'.format(pattern, src, pattern.matches(path))) + include_copy_list = [p3d.GlobPattern(i) for i in self.include_paths] + + def check_pattern(src, pattern_list): + # Normalize file paths across platforms + path = p3d.Filename.from_os_specific(os.path.normpath(src)).get_fullpath() + for pattern in pattern_list: + #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches(path))) if pattern.matches(path): return True return False - def dir_has_files(directory): - files = [ - i for i in os.listdir(directory) - if not check_pattern(os.path.normpath(os.path.join(directory, i))) - ] - return bool(files) + def check_file(fname): + return check_pattern(fname, include_copy_list) and \ + not check_pattern(fname, ignore_copy_list) def copy_file(src, dst): src = os.path.normpath(src) dst = os.path.normpath(dst) - if check_pattern(src): + if not check_file(src): self.announce('skipping file {}'.format(src)) return @@ -357,28 +360,21 @@ class build_apps(distutils.core.Command): self.announce('copying {0} -> {1}'.format(src, dst)) shutil.copyfile(src, dst) - def copy_dir(src, dst): - for item in os.listdir(src): - s = os.path.join(src, item) - d = os.path.join(dst, item) - if os.path.isfile(s): - copy_file(s, d) - elif not dir_has_files(s): - self.announce('skipping directory'.format(os.path.normpath(s))) - else: - copy_dir(s, d) + def update_path(path): + normpath = p3d.Filename.from_os_specific(os.path.normpath(src)).c_str() + for inputpath, outputpath in self.rename_paths.items(): + if normpath.startswith(inputpath): + normpath = normpath.replace(inputpath, outputpath, 1) + return p3d.Filename(normpath).to_os_specific() - for path in self.copy_paths: - if isinstance(path, basestring): - src = dst = path - else: - src, dst = path - dst = os.path.join(builddir, dst) + rootdir = os.getcwd() + for dirname, subdirlist, filelist in os.walk(rootdir): + dirpath = os.path.relpath(dirname, rootdir) + for fname in filelist: + src = os.path.join(dirpath, fname) + dst = os.path.join(builddir, update_path(src)) - if os.path.isfile(src): copy_file(src, dst) - else: - copy_dir(src, dst) def add_dependency(self, name, target_dir, search_path, referenced_by): """ Searches for the given DLL on the search path. If it exists, diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 973f12b381..31c14c485f 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -4,13 +4,17 @@ setup( name="asteroids", options = { 'build_apps': { - 'copy_paths': ['.'], - 'exclude_paths': ['build/*', 'setup.py', 'requirements.txt', 'wheels/*', '*.swp'], + 'include_paths': [ + '*.png', + '*.jpg', + '*.egg', + ], 'gui_apps': { 'asteroids': 'main.py', }, 'plugins': [ 'pandagl', + 'p3openal_audio', ], 'deploy_platforms': [ 'manylinux1_x86_64', From e827120e466fb2732a0666bc7865a14e48e8aab4 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 22 Nov 2017 18:46:48 -0800 Subject: [PATCH 154/424] deploy-ng: Do not warn about missing manylinux1 system libraries --- direct/src/showutil/dist.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index cb02baa2f3..caa5507b6f 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -47,13 +47,20 @@ class build_apps(distutils.core.Command): '.egg': ('.egg.bam', 'egg2bam -o {1} {0}'), } self.exclude_dependencies = [ + # Windows 'kernel32.dll', 'user32.dll', 'wsock32.dll', 'ws2_32.dll', 'advapi32.dll', 'opengl32.dll', 'glu32.dll', 'gdi32.dll', 'shell32.dll', 'ntdll.dll', 'ws2help.dll', 'rpcrt4.dll', 'imm32.dll', 'ddraw.dll', 'shlwapi.dll', 'secur32.dll', 'dciman32.dll', 'comdlg32.dll', 'comctl32.dll', 'ole32.dll', 'oleaut32.dll', 'gdiplus.dll', 'winmm.dll', 'iphlpapi.dll', - 'msvcrt.dll', 'kernelbase.dll', 'msimg32.dll', 'msacm32.dll'] + 'msvcrt.dll', 'kernelbase.dll', 'msimg32.dll', 'msacm32.dll', + + # manylinux1 + 'libdl.so.2', 'libstdc++.so.6', 'libm.so.6', 'libgcc_s.so.1', + 'libpthread.so.0', 'libc.so.6', 'ld-linux-x86-64.so.2', + 'libgl.so.1', 'libx11.so.6', 'libreadline.so.5', 'libncursesw.so.5', + ] # We keep track of the zip files we've opened. self._zip_files = {} From 6acbeb8176bc2efbd4eec9fa637e966ce1608a6f Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 23 Nov 2017 12:53:36 -0800 Subject: [PATCH 155/424] deploy-ng: Fix error when running bdist_apps --- direct/src/showutil/dist.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index caa5507b6f..e6a3581c40 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -635,12 +635,14 @@ class bdist_apps(distutils.core.Command): build_dir = os.path.join(build_base, platform) base_dir = self.distribution.get_name() temp_dir = os.path.join(build_base, base_dir) - archive_format = 'gztar' if platform.startswith('linux') else 'zip' + archive_format = 'gztar' if 'linux' in platform else 'zip' basename = '{}_{}'.format(self.distribution.get_fullname(), platform) if (os.path.exists(temp_dir)): shutil.rmtree(temp_dir) - shutil.copy_tree(build_dir, temp_dir) + shutil.copytree(build_dir, temp_dir) + + self.announce('Building {} for platform: {}'.format(archive_format, platform), distutils.log.INFO) distutils.archive_util.make_archive(basename, archive_format, root_dir=build_base, base_dir=base_dir) From 883c6555ecfc8fa4a518a2c5ce261675cfea0507 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 23 Nov 2017 12:53:56 -0800 Subject: [PATCH 156/424] deploy-ng: Add prints for progress in build_apps Also, print out the platform when we fail to get a panda3d wheel. --- direct/src/showutil/dist.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index e6a3581c40..6b908a0716 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -131,11 +131,13 @@ class build_apps(distutils.core.Command): self.build_runtimes(platform, use_wheels) def download_wheels(self, platform): - """ Downloads panda3d wheels for the given platform using pip. - These are special wheels that are expected to contain a deploy_libs + """ Downloads wheels for the given platform using pip. This includes panda3d + wheels. These are special wheels that are expected to contain a deploy_libs directory containing the Python runtime libraries, which will be added to sys.path.""" + self.announce('Gathering wheels for platform: {}'.format(platform), distutils.log.INFO) + whldir = os.path.join(self.build_base, '__whl_cache__') abi_tag = pip.pep425tags.get_abi_tag() @@ -184,7 +186,7 @@ class build_apps(distutils.core.Command): p3dwhl = self._get_zip_file(p3dwhlfn) break else: - raise RuntimeError("Missing panda3d wheel") + raise RuntimeError("Missing panda3d wheel for platform: {}".format(platform)) #whlfiles = {whl: self._get_zip_file(whl) for whl in wheelpaths} @@ -195,6 +197,9 @@ class build_apps(distutils.core.Command): # Add deploy_libs from panda3d whl to the path path.insert(0, os.path.join(p3dwhlfn, 'deploy_libs')) + + self.announce('Building runtime for platform: {}'.format(platform), distutils.log.INFO) + # Create runtimes freezer_extras = set() freezer_modules = set() @@ -317,6 +322,7 @@ class build_apps(distutils.core.Command): self.copy_with_dependencies(source_path, target_path, search_path) # Copy Game Files + self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO) ignore_copy_list = [ '__pycache__', '*.pyc', From e03c249cc5931ee8b0f1d915eab9ccff40ac996f Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Nov 2017 17:47:22 +0100 Subject: [PATCH 157/424] makepanda: write target platform to file, rebuild if platform changes --- makepanda/makepanda.py | 66 ++++++++++++++++++++++++++++++++++++++ makepanda/makepandacore.py | 6 +++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 7efa6ea1f7..d9e4755412 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -22,6 +22,7 @@ except: from makepandacore import * from installpanda import * +from distutils.util import get_platform import time import os import sys @@ -68,6 +69,7 @@ WINDOWS_SDK = None MSVC_VERSION = None BOOUSEINTELCOMPILER = False OPENCV_VER_23 = False +PLATFORM = None if "MACOSX_DEPLOYMENT_TARGET" in os.environ: OSXTARGET=os.environ["MACOSX_DEPLOYMENT_TARGET"] @@ -384,6 +386,67 @@ MAJOR_VERSION = '.'.join(VERSION.split('.')[:2]) if P3DSUFFIX is None: P3DSUFFIX = MAJOR_VERSION +# Now determine the distutils-style platform tag for the target system. +target = GetTarget() +if target == 'windows': + if GetTargetArch() == 'x64': + PLATFORM = 'win-amd64' + else: + PLATFORM = 'win32' + +elif target == 'darwin': + if OSXTARGET: + osxver = OSXTARGET + else: + maj, min = platform.mac_ver()[0].split('.')[:2] + osxver = int(maj), int(min) + + arch_tag = None + if not OSX_ARCHS: + arch_tag = GetTargetArch() + elif len(OSX_ARCHS) == 1: + arch_tag = OSX_ARCHS[0] + elif frozenset(OSX_ARCHS) == frozenset(('i386', 'ppc')): + arch_tag = 'fat' + elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386')): + arch_tag = 'intel' + elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'ppc64')): + arch_tag = 'fat64' + elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386', 'ppc')): + arch_tag = 'fat32' + else: + raise RuntimeError('No arch tag for arch combination %s' % OSX_ARCHS) + + PLATFORM = 'macosx-{0}.{1}-{2}'.format(osxver[0], osxver[1], arch_tag) + +elif target == 'linux' and os.path.isfile("/lib/libc-2.5.so") and os.path.isdir("/opt/python"): + # This is manylinux1. A bit of a sloppy check, though. + if GetTargetArch() in ('x86_64', 'amd64'): + PLATFORM = 'manylinux1-x86_64' + else: + PLATFORM = 'manylinux1-i686' + +elif not CrossCompiling(): + if HasTargetArch(): + # Replace the architecture in the platform string. + platform_parts = get_platform().rsplit('-', 1) + target_arch = GetTargetArch() + if target_arch == 'amd64': + target_arch = 'x86_64' + PLATFORM = platform_parts[0] + '-' + target_arch + else: + # We're not cross-compiling; just take the host arch. + PLATFORM = get_platform() + +else: + target_arch = GetTargetArch() + if target_arch == 'amd64': + target_arch = 'x86_64' + PLATFORM = '{0}-{1}' % (target, target_arch) + + +print("Platform: %s" % PLATFORM) + outputdir_suffix = "" if (RUNTIME or RTDIST): @@ -2485,6 +2548,9 @@ def WriteConfigSettings(): if (PkgSkip(x)): ConditionalWriteFile(GetOutputDir() + '/tmp/dtool_have_'+x.lower()+'.dat', "0\n") else: ConditionalWriteFile(GetOutputDir() + '/tmp/dtool_have_'+x.lower()+'.dat', "1\n") + # Finally, write a platform.dat with the platform we are compiling for. + ConditionalWriteFile(GetOutputDir() + '/tmp/platform.dat', PLATFORM) + WriteConfigSettings() WarnConflictingFiles() diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index 6705c72a1f..bfb2600f08 100644 --- a/makepanda/makepandacore.py +++ b/makepanda/makepandacore.py @@ -3191,10 +3191,14 @@ def TargetAdd(target, dummy=0, opts=[], input=[], dep=[], ipath=None, winrc=None if winrc and GetTarget() == 'windows': TargetAdd(target, input=WriteResourceFile(target.split("/")[-1].split(".")[0], **winrc)) - if target.endswith(".in"): + ext = os.path.splitext(target)[1] + if ext == ".in": if not CrossCompiling(): t.deps[FindLocation("interrogate.exe", [])] = 1 t.deps[FindLocation("dtool_have_python.dat", [])] = 1 + if ext in (".obj", ".tlb", ".res", ".plugin", ".app") or ext in SUFFIX_DLL or ext in SUFFIX_LIB: + t.deps[FindLocation("platform.dat", [])] = 1 + if target.endswith(".pz") and not CrossCompiling(): t.deps[FindLocation("pzip.exe", [])] = 1 From a7e00d0ee609d60c0dd9839218241cdc7f77d50a Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Nov 2017 17:48:25 +0100 Subject: [PATCH 158/424] makewheel: use platform from build --- makepanda/makewheel.py | 45 +++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index c17cae3282..44f0f26274 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -24,14 +24,6 @@ from makepandacore import ColorText, LocateBinary, ParsePandaVersion, GetExtensi from base64 import urlsafe_b64encode -default_platform = get_platform() - -if default_platform.startswith("linux-"): - # Is this manylinux1? - if os.path.isfile("/lib/libc-2.5.so") and os.path.isdir("/opt/python"): - default_platform = default_platform.replace("linux", "manylinux1") - - def get_abi_tag(): if sys.version_info >= (3, 0): soabi = get_config_var('SOABI') @@ -68,16 +60,18 @@ def is_elf_file(path): open(path, 'rb').read(4) == b'\x7FELF' -def is_mach_o_file(path): +def is_macho_or_fat_file(path): base = os.path.basename(path) return os.path.isfile(path) and '.' not in base and \ - open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA', - b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE', - b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE') + open(path, 'rb').read(4) in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE', + b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE', + b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA', + b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA') def is_fat_file(path): return os.path.isfile(path) and \ - open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA') + open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA', + b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA') def get_python_ext_module_dir(): @@ -88,11 +82,10 @@ def get_python_ext_module_dir(): if sys.platform in ('win32', 'cygwin'): is_executable = is_exe_file elif sys.platform == 'darwin': - is_executable = is_mach_o_file + is_executable = is_macho_or_fat_file else: is_executable = is_elf_file - # Other global parameters PY_VERSION = "cp{0}{1}".format(*sys.version_info) ABI_TAG = get_abi_tag() @@ -351,7 +344,10 @@ class WheelFile(object): temp = tempfile.NamedTemporaryFile(suffix=suffix, prefix='whl', delete=False) # On macOS, if no fat wheel was requested, extract the right architecture. - if sys.platform == "darwin" and is_fat_file(source_path) and not self.platform.endswith("_intel"): + if sys.platform == "darwin" and is_fat_file(source_path) \ + and not self.platform.endswith("_intel") \ + and "_fat" not in self.platform: + if self.platform.endswith("_x86_64"): arch = 'x86_64' else: @@ -452,11 +448,24 @@ class WheelFile(object): self.zip_file.close() -def makewheel(version, output_dir, platform=default_platform): +def makewheel(version, output_dir, platform=None): if sys.platform not in ("win32", "darwin") and not sys.platform.startswith("cygwin"): if not LocateBinary("patchelf"): raise Exception("patchelf is required when building a Linux wheel.") + if platform is None: + # Determine the platform from the build. + platform_dat = os.path.join(output_dir, 'tmp', 'platform.dat') + if os.path.isfile(platform_dat): + platform = open(platform_dat, 'r').read().strip() + else: + print("Could not find platform.dat in build directory") + platform = get_platform() + if platform.startswith("linux-"): + # Is this manylinux1? + if os.path.isfile("/lib/libc-2.5.so") and os.path.isdir("/opt/python"): + platform = platform.replace("linux", "manylinux1") + platform = platform.replace('-', '_').replace('.', '_') # Global filepaths @@ -616,7 +625,7 @@ if __name__ == "__main__": parser.add_option('', '--version', dest = 'version', help = 'Panda3D version number (default: %s)' % (version), default = version) parser.add_option('', '--outputdir', dest = 'outputdir', help = 'Makepanda\'s output directory (default: built)', default = 'built') parser.add_option('', '--verbose', dest = 'verbose', help = 'Enable verbose output', action = 'store_true', default = False) - parser.add_option('', '--platform', dest = 'platform', help = 'Override platform tag (default: %s)' % (default_platform), default = get_platform()) + parser.add_option('', '--platform', dest = 'platform', help = 'Override platform tag', default = None) (options, args) = parser.parse_args() SetVerbose(options.verbose) From d755de849cae10e62941f370a80004d855012834 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Nov 2017 21:48:18 +0100 Subject: [PATCH 159/424] deploy-ng: new extensible blob format with PRC configurability --- direct/src/showutil/FreezeTool.py | 361 +++++++++++++++++++++--- direct/src/showutil/dist.py | 81 ++++-- direct/src/showutil/pefile.py | 41 ++- dtool/src/prc/configPageManager.cxx | 77 ++++- makepanda/makepanda.py | 2 +- pandatool/src/deploy-stub/deploy-stub.c | 150 +++++++--- 6 files changed, 610 insertions(+), 102 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index a8a967f93a..78645acae9 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -8,10 +8,12 @@ import marshal import imp import platform import struct -from io import StringIO, TextIOWrapper +from io import StringIO, BytesIO, TextIOWrapper import distutils.sysconfig as sysconf import zipfile +from . import pefile + # Temporary (?) try..except to protect against unbuilt p3extend_frozen. try: import p3extend_frozen @@ -1589,7 +1591,7 @@ class Freezer: return target - def generateRuntimeFromStub(self, basename, stub_file): + def generateRuntimeFromStub(self, basename, stub_file, fields={}): # We must have a __main__ module to make an exe file. if not self.__writingModule('__main__'): message = "Can't generate an executable without a __main__ module." @@ -1602,15 +1604,18 @@ class Freezer: target = basename modext = '.so' - address_offset = 0 - - # First gather up the strings for all the module names. - blob = b"" + # First gather up the strings and code for all the module names, and + # put those in a string pool. + pool = b"" strings = set() for moduleName, mdef in self.getModuleDefs(): strings.add(moduleName.encode('ascii')) + for value in fields.values(): + if value is not None: + strings.add(value.encode('utf-8')) + # Sort by length descending, allowing reuse of partial strings. strings = sorted(strings, key=lambda str:-len(str)) string_offsets = {} @@ -1618,13 +1623,15 @@ class Freezer: for string in strings: # First check whether it's already in there; it could be part of # a longer string. - offset = blob.find(string + b'\0') + offset = pool.find(string + b'\0') if offset < 0: - offset = len(blob) - blob += string + b'\0' + offset = len(pool) + pool += string + b'\0' string_offsets[string] = offset - # Generate export table. + # Now go through the modules and add them to the pool as well. These + # are not 0-terminated, but we later record their sizes and names in + # a table after the blob header. moduleList = [] for moduleName, mdef in self.getModuleDefs(): @@ -1635,9 +1642,9 @@ class Freezer: continue # For whatever it's worth, align the code blocks. - if len(blob) & 3 != 0: - pad = (4 - (len(blob) & 3)) - blob += b'\0' * pad + if len(pool) & 3 != 0: + pad = (4 - (len(pool) & 3)) + pool += b'\0' * pad assert not mdef.exclude # Allow importing this module. @@ -1649,8 +1656,8 @@ class Freezer: if getattr(module, "__path__", None): # Indicate package by negative size size = -size - moduleList.append((moduleName, len(blob), size)) - blob += code + moduleList.append((moduleName, len(pool), size)) + pool += code continue # This is a module with no associated Python code. It is either @@ -1668,55 +1675,327 @@ class Freezer: code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) code = compile(code, moduleName, 'exec') code = marshal.dumps(code) - moduleList.append((moduleName, len(blob), len(code))) - blob += code + moduleList.append((moduleName, len(pool), len(code))) + pool += code - # Now compose the final blob. - if '64' in self.platform: - layout = '= 0xff00 and shndx <= 0xffff: + assert False + else: + # Got it. Make the replacement. + off = section_offsets[shndx] + value + elf_data[off:off+len(replacement)] = replacement + replaced = True + + return replaced + + def _find_symbol_macho(self, macho_data, symbol_name): + """ Returns the offset of the given symbol in the binary file. """ + + if macho_data[:4] in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'): + endian = '<' + else: + endian = '>' + + cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ + struct.unpack_from(endian + 'IIIIII', macho_data, 4) + + is_64bit = (cputype & 0x1000000) != 0 + segments = [] + + cmd_ptr = 28 + nlist_struct = endian + 'IBBHI' + if is_64bit: + nlist_struct = endian + 'IBBHQ' + cmd_ptr += 4 + nlist_size = struct.calcsize(nlist_struct) + + for i in range(ncmds): + cmd, cmd_size = struct.unpack_from(endian + 'II', macho_data, cmd_ptr) + cmd_data = macho_data[cmd_ptr+8:cmd_ptr+cmd_size] + cmd_ptr += cmd_size + + cmd &= ~0x80000000 + + if cmd == 0x01: # LC_SEGMENT + segname, vmaddr, vmsize, fileoff, filesize, maxprot, initprot, nsects, flags = \ + struct.unpack_from(endian + '16sIIIIIIII', cmd_data) + segments.append((vmaddr, vmsize, fileoff)) + + elif cmd == 0x19: # LC_SEGMENT_64 + segname, vmaddr, vmsize, fileoff, filesize, maxprot, initprot, nsects, flags = \ + struct.unpack_from(endian + '16sQQQQIIII', cmd_data) + segments.append((vmaddr, vmsize, fileoff)) + + elif cmd == 0x2: # LC_SYMTAB + symoff, nsyms, stroff, strsize = \ + struct.unpack_from(endian + 'IIII', cmd_data) + + strings = macho_data[stroff:stroff+strsize] + + for i in range(nsyms): + strx, type, sect, desc, value = struct.unpack_from(nlist_struct, macho_data, symoff) + symoff += nlist_size + name = strings[strx : strings.find(b'\0', strx)] + + if name == symbol_name: + # Find out in which segment this is. + for vmaddr, vmsize, fileoff in segments: + # Is it defined in this segment? + rel = value - vmaddr + if rel >= 0 and rel < vmsize: + # Yes, so return the symbol offset. + return fileoff + rel + + def _replace_symbol_fat(self, fat_data, symbol_name, replacement, is_64bit): + """ Implementation of _replace_symbol for universal binaries. """ + num_fat, = struct.unpack_from('>I', fat_data, 4) + + # After the header we get a table of executables in this fat file, + # each one with a corresponding offset into the file. + replaced = False + ptr = 8 + for i in range(num_fat): + if is_64bit: + cputype, cpusubtype, offset, size, align = \ + struct.unpack_from('>QQQQQ', fat_data, ptr) + ptr += 40 + else: + cputype, cpusubtype, offset, size, align = \ + struct.unpack_from('>IIIII', fat_data, ptr) + ptr += 20 + + macho_data = fat_data[offset:offset+size] + off = self._find_symbol_macho(macho_data, symbol_name) + if off is not None: + off += offset + fat_data[off:off+len(replacement)] = replacement + replaced = True + + return replaced + def makeModuleDef(self, mangledName, code): result = '' result += 'static unsigned char %s[] = {' % (mangledName) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 6b908a0716..4b37f88ab4 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -225,7 +225,17 @@ class build_apps(distutils.core.Command): stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name) stub_file = open(stub_path, 'rb') - freezer.generateRuntimeFromStub(os.path.join(builddir, appname), stub_file) + freezer.generateRuntimeFromStub(os.path.join(builddir, appname), stub_file, { + 'prc_data': None, + 'default_prc_dir': None, + 'prc_dir_envvars': None, + 'prc_path_envvars': None, + 'prc_patterns': None, + 'prc_encrypted_patterns': None, + 'prc_encryption_key': None, + 'prc_executable_patterns': None, + 'prc_executable_args_envvar': None, + }) stub_file.close() freezer_extras.update(freezer.extras) @@ -492,16 +502,22 @@ class build_apps(distutils.core.Command): # Elf magic. Used on (among others) Linux and FreeBSD. deps = self._read_dependencies_elf(fp, os.path.dirname(source_path), search_path) - elif magic in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE', - b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'): + elif magic in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'): # A Mach-O file, as used on macOS. - deps = self._read_dependencies_macho(fp) + deps = self._read_dependencies_macho(fp, '<') - elif magic in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA'): + elif magic in (b'\xFE\xED\xFA\xCE', b'\xFE\xED\xFA\xCF'): + deps = self._read_dependencies_macho(fp, '>') + + elif magic in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA'): # A fat file, containing multiple Mach-O binaries. In the future, # we may want to extract the one containing the architecture we # are building for. - deps = self._read_dependencies_fat(fp) + deps = self._read_dependencies_fat(fp, False) + + elif magic in (b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'): + # A 64-bit fat file. + deps = self._read_dependencies_fat(fp, True) # If we discovered any dependencies, recursively add those. if deps: @@ -573,23 +589,23 @@ class build_apps(distutils.core.Command): search_path += rpath return needed - def _read_dependencies_macho(self, fp): + def _read_dependencies_macho(self, fp, endian): """ Having read the first 4 bytes of the Mach-O file, fetches the dependent libraries and returns those as a list. """ cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ - struct.unpack('I', fp.read(4))[0] - if num_fat == 0: - return [] + def _read_dependencies_fat(self, fp, is_64bit): + num_fat, = struct.unpack('>I', fp.read(4)) # After the header we get a table of executables in this fat file, # each one with a corresponding offset into the file. - # We are just interested in the first one for now. - cputype, cpusubtype, offset, size, align = \ - struct.unpack('>IIIII', fp.read(20)) + offsets = [] + for i in range(num_fat): + if is_64bit: + cputype, cpusubtype, offset, size, align = \ + struct.unpack('>QQQQQ', fp.read(40)) + else: + cputype, cpusubtype, offset, size, align = \ + struct.unpack('>IIIII', fp.read(20)) + offsets.append(offset) - # Add 4, since it expects we've already read the magic. - fp.seek(offset + 4) - return self._read_dependencies_macho(fp) + # Go through each of the binaries in the fat file. + deps = [] + for offset in offsets: + # Add 4, since it expects we've already read the magic. + fp.seek(offset) + magic = fp.read(4) + + if magic in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'): + endian = '<' + elif magic in (b'\xFE\xED\xFA\xCE', b'\xFE\xED\xFA\xCF'): + endian = '>' + else: + # Not a Mach-O file we can read. + continue + + for dep in self._read_dependencies_macho(fp, endian): + if dep not in deps: + deps.append(dep) + + return deps class bdist_apps(distutils.core.Command): diff --git a/direct/src/showutil/pefile.py b/direct/src/showutil/pefile.py index 20af71c4b7..03714b82fb 100755 --- a/direct/src/showutil/pefile.py +++ b/direct/src/showutil/pefile.py @@ -19,6 +19,7 @@ if sys.version_info >= (3, 0): # Define some internally used structures. RVASize = namedtuple('RVASize', ('addr', 'size')) impdirtab = namedtuple('impdirtab', ('lookup', 'timdat', 'forward', 'name', 'impaddr')) +expdirtab = namedtuple('expdirtab', ('flags', 'timdat', 'majver', 'minver', 'name', 'ordinal_base', 'nentries', 'nnames', 'entries', 'names', 'ordinals')) def _unpack_zstring(mem, offs=0): @@ -571,10 +572,11 @@ class PEFile(object): # Read the sections into some kind of virtual memory. self.vmem = bytearray(self.sections[-1].vaddr + self.sections[-1].size) + memview = memoryview(self.vmem) for section in self.sections: fp.seek(section.offset) - fp.readinto(memoryview(self.vmem)[section.vaddr:section.vaddr+section.size]) + fp.readinto(memview[section.vaddr:section.vaddr+section.size]) # Read the import table. start = self.imp_rva.addr @@ -596,6 +598,43 @@ class PEFile(object): if self.res_rva.addr and self.res_rva.size: self.resources.unpack_from(self.vmem, self.res_rva.addr) + def get_export_address(self, symbol_name): + """ Finds the virtual address for a named export symbol. """ + + start = self.exp_rva.addr + expdir = expdirtab(*unpack('= 0 and ordinal < expdir.nentries + start = expdir.entries + 8 * ordinal + addr, = unpack('= section.vaddr and addr < section.vaddr + section.size: + return section + def add_icon(self, icon, ordinal=2): """ Adds an icon resource from the given Icon object. Requires calling add_resource_section() afterwards. """ diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index f7868aa394..7416e0fcbe 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -37,6 +37,10 @@ #include #include +#ifndef _MSC_VER +#include +#endif + ConfigPageManager *ConfigPageManager::_global_ptr = NULL; /** @@ -90,11 +94,44 @@ reload_implicit_pages() { } _implicit_pages.clear(); + // If we are running inside a deployed application, see if it exposes + // information about how the PRC data should be initialized. + struct BlobInfo { + uint64_t blob_offset; + uint64_t blob_size; + uint16_t version; + uint16_t num_pointers; + uint16_t codepage; + uint16_t flags; + uint64_t reserved; + const void *module_table; + const char *prc_data; + const char *default_prc_dir; + const char *prc_dir_envvars; + const char *prc_path_envvars; + const char *prc_patterns; + const char *prc_encrypted_patterns; + const char *prc_encryption_key; + const char *prc_executable_patterns; + const char *prc_executable_args_envvar; + }; +#ifdef _MSC_VER + const BlobInfo *blobinfo = (const BlobInfo *)GetProcAddress(GetModuleHandle(NULL), "blobinfo"); +#else + const BlobInfo *blobinfo = (const BlobInfo *)dlsym(RTLD_SELF, "blobinfo"); +#endif + if (blobinfo != nullptr && (blobinfo->version == 0 || blobinfo->num_pointers < 10)) { + blobinfo = nullptr; + } + // PRC_PATTERNS lists one or more filename templates separated by spaces. // Pull them out and store them in _prc_patterns. _prc_patterns.clear(); string prc_patterns = PRC_PATTERNS; + if (blobinfo != nullptr && blobinfo->prc_patterns != nullptr) { + prc_patterns = blobinfo->prc_patterns; + } if (!prc_patterns.empty()) { vector_string pat_list; ConfigDeclaration::extract_words(prc_patterns, pat_list); @@ -114,6 +151,9 @@ reload_implicit_pages() { _prc_encrypted_patterns.clear(); string prc_encrypted_patterns = PRC_ENCRYPTED_PATTERNS; + if (blobinfo != nullptr && blobinfo->prc_encrypted_patterns != nullptr) { + prc_encrypted_patterns = blobinfo->prc_encrypted_patterns; + } if (!prc_encrypted_patterns.empty()) { vector_string pat_list; ConfigDeclaration::extract_words(prc_encrypted_patterns, pat_list); @@ -131,6 +171,9 @@ reload_implicit_pages() { _prc_executable_patterns.clear(); string prc_executable_patterns = PRC_EXECUTABLE_PATTERNS; + if (blobinfo != nullptr && blobinfo->prc_executable_patterns != nullptr) { + prc_executable_patterns = blobinfo->prc_executable_patterns; + } if (!prc_executable_patterns.empty()) { vector_string pat_list; ConfigDeclaration::extract_words(prc_executable_patterns, pat_list); @@ -151,6 +194,9 @@ reload_implicit_pages() { // spaces. Pull them out, and each of those contains the name of a single // directory to search. Add it to the search path. string prc_dir_envvars = PRC_DIR_ENVVARS; + if (blobinfo != nullptr && blobinfo->prc_dir_envvars != nullptr) { + prc_dir_envvars = blobinfo->prc_dir_envvars; + } if (!prc_dir_envvars.empty()) { vector_string prc_dir_envvar_list; ConfigDeclaration::extract_words(prc_dir_envvars, prc_dir_envvar_list); @@ -170,6 +216,9 @@ reload_implicit_pages() { // spaces. Pull them out, and then each one of those contains a list of // directories to search. Add each of those to the search path. string prc_path_envvars = PRC_PATH_ENVVARS; + if (blobinfo != nullptr && blobinfo->prc_path_envvars != nullptr) { + prc_path_envvars = blobinfo->prc_path_envvars; + } if (!prc_path_envvars.empty()) { vector_string prc_path_envvar_list; ConfigDeclaration::extract_words(prc_path_envvars, prc_path_envvar_list); @@ -201,7 +250,7 @@ reload_implicit_pages() { * DEFAULT_PATHSEP. */ string prc_path2_envvars = PRC_PATH2_ENVVARS; - if (!prc_path2_envvars.empty()) { + if (!prc_path2_envvars.empty() && blobinfo == nullptr) { vector_string prc_path_envvar_list; ConfigDeclaration::extract_words(prc_path2_envvars, prc_path_envvar_list); for (size_t i = 0; i < prc_path_envvar_list.size(); ++i) { @@ -225,6 +274,9 @@ reload_implicit_pages() { // If nothing's on the search path (PRC_DIR and PRC_PATH were not // defined), then use the DEFAULT_PRC_DIR. string default_prc_dir = DEFAULT_PRC_DIR; + if (blobinfo != nullptr && blobinfo->default_prc_dir != nullptr) { + default_prc_dir = blobinfo->default_prc_dir; + } if (!default_prc_dir.empty()) { // It's already from-os-specific by ppremake. Filename prc_dir_filename = default_prc_dir; @@ -297,12 +349,24 @@ reload_implicit_pages() { } } + int i = 1; + + // If prc_data is predefined, we load it as an implicit page. + if (blobinfo != nullptr && blobinfo->prc_data != nullptr) { + ConfigPage *page = new ConfigPage("builtin", true, i); + ++i; + _implicit_pages.push_back(page); + _pages_sorted = false; + + istringstream in(blobinfo->prc_data); + page->read_prc(in); + } + // Now we have a list of filenames in order from most important to least // important. Walk through the list in reverse order to load their // contents, because we want the first file in the list (the most important) // to be on the top of the stack. ConfigFiles::reverse_iterator ci; - int i = 1; for (ci = config_files.rbegin(); ci != config_files.rend(); ++ci) { const ConfigFile &file = (*ci); Filename filename = file._filename; @@ -313,6 +377,9 @@ reload_implicit_pages() { string command = filename.to_os_specific(); string envvar = PRC_EXECUTABLE_ARGS_ENVVAR; + if (blobinfo != nullptr && blobinfo->prc_executable_args_envvar != nullptr) { + envvar = blobinfo->prc_executable_args_envvar; + } if (!envvar.empty()) { string args = ExecutionEnvironment::get_environment_variable(envvar); if (!args.empty()) { @@ -343,7 +410,11 @@ reload_implicit_pages() { _implicit_pages.push_back(page); _pages_sorted = false; - page->read_encrypted_prc(in, PRC_ENCRYPTION_KEY); + if (blobinfo != nullptr && blobinfo->prc_encryption_key != nullptr) { + page->read_encrypted_prc(in, blobinfo->prc_encryption_key); + } else { + page->read_encrypted_prc(in, PRC_ENCRYPTION_KEY); + } } } else if ((file._file_flags & FF_read) != 0) { diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index d9e4755412..383e9b652c 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6583,7 +6583,7 @@ if PkgSkip("PYTHON") == 0: if GetTarget() == 'windows': TargetAdd('frozen_dllmain.obj', opts=OPTS, input='frozen_dllmain.c') - if GetTarget() == 'linux': + if GetTarget() == 'linux' or GetTarget() == 'freebsd': # Setup rpath so libs can be found in the same directory as the deployed game LibName('DEPLOYSTUB', "-Wl,-rpath,\$ORIGIN") LibName('DEPLOYSTUB', "-Wl,-z,origin") diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 51d7da88a9..99a0e361ba 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -22,6 +22,29 @@ #endif #endif +/* Define an exposed symbol where we store the offset to the module data. */ +#ifdef _MSC_VER +__declspec(dllexport) +#else +__attribute__((__visibility__("default"), used)) +#endif +volatile struct { + uint64_t blob_offset; + uint64_t blob_size; + uint16_t version; + uint16_t num_pointers; + uint16_t codepage; + uint16_t flags; + uint64_t reserved; + + // Leave room for future expansion. We only read pointer 0, but there are + // other pointers that are being read by configPageManager.cxx. + void *pointers[24]; + + // The reason we initialize it to -1 is because otherwise, smart linkers may + // end up putting it in the .bss section for zero-initialized data. +} blobinfo = {(uint64_t)-1}; + #ifdef MS_WINDOWS #define WIN32_LEAN_AND_MEAN #include @@ -221,15 +244,12 @@ error: return sts; } - -#if defined(_WIN32) && PY_MAJOR_VERSION >= 3 -int wmain(int argc, wchar_t *argv[]) { -#else -int main(int argc, char *argv[]) { -#endif - struct _frozen *blob, *moddef; - uint64_t begin, end, size; - int retval; +/** + * Maps the binary blob at the given memory address to memory, and returns the + * pointer to the beginning of it. + */ +static void *map_blob(off_t offset, size_t size) { + void *blob; FILE *runtime; #ifdef _WIN32 @@ -243,7 +263,7 @@ int main(int argc, char *argv[]) { mib[3] = getpid(); if (sysctl(mib, 4, (void *)buffer, &bufsize, NULL, 0) == -1) { perror("sysctl"); - return 1; + return NULL; } runtime = fopen(buffer, "rb"); #else @@ -251,46 +271,108 @@ int main(int argc, char *argv[]) { runtime = fopen(argv[0], "rb"); #endif - // Get offsets - fseek(runtime, -8, SEEK_END); - end = ftell(runtime); - fread(&begin, 8, 1, runtime); - size = end - begin; + // Get offsets. In version 0, we read it from the end of the file. + if (blobinfo.version == 0) { + uint64_t end, begin; + fseek(runtime, -8, SEEK_END); + end = ftell(runtime); + fread(&begin, 8, 1, runtime); + + offset = (off_t)begin; + size = (size_t)(end - begin); + } // mmap the section indicated by the offset (or malloc/fread on windows) #ifdef _WIN32 - blob = (struct _frozen *)malloc(size); + blob = (void *)malloc(size); assert(blob != NULL); - fseek(runtime, (long)begin, SEEK_SET); + fseek(runtime, (long)offset, SEEK_SET); fread(blob, size, 1, runtime); #else - blob = (struct _frozen *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), begin); - assert(blob != NULL); + blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset); + assert(blob != MAP_FAILED); #endif fclose(runtime); + return blob; +} - // Offset the pointers in the table using the base mmap address. - moddef = blob; - while (moddef->name) { - moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob); - if (moddef->code != 0) { - moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob); +/** + * The inverse of map_blob. + */ +static void unmap_blob(void *blob) { + if (blob) { +#ifdef _WIN32 + free(blob); +#else + munmap(blob, blobinfo.blob_size); +#endif + } +} + +/** + * Main entry point to deploy-stub. + */ +#if defined(_WIN32) && PY_MAJOR_VERSION >= 3 +int wmain(int argc, wchar_t *argv[]) { +#else +int main(int argc, char *argv[]) { +#endif + int retval; + struct _frozen *moddef; + void *blob = NULL; + + /* + printf("blob_offset: %d\n", (int)blobinfo.blob_offset); + printf("blob_size: %d\n", (int)blobinfo.blob_size); + printf("version: %d\n", (int)blobinfo.version); + printf("num_pointers: %d\n", (int)blobinfo.num_pointers); + printf("codepage: %d\n", (int)blobinfo.codepage); + printf("flags: %d\n", (int)blobinfo.flags); + printf("reserved: %d\n", (int)blobinfo.reserved); + */ + + // If we have a blob offset, we have to map the blob to memory. + if (blobinfo.version == 0 || blobinfo.blob_offset != 0) { + void *blob = map_blob((off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size); + assert(blob != NULL); + + // Offset the pointers in the header using the base mmap address. + if (blobinfo.version > 0 && blobinfo.num_pointers > 0) { + uint32_t i; + for (i = 0; i < blobinfo.num_pointers; ++i) { + if (i == 0 || blobinfo.pointers[i] != 0) { + blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob); + } + } + } else { + blobinfo.pointers[0] = blob; + } + + // Offset the pointers in the module table using the base mmap address. + moddef = blobinfo.pointers[0]; + while (moddef->name) { + moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob); + if (moddef->code != 0) { + moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob); + } + //printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size); + moddef++; } - //printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size); - moddef++; } +#ifdef _WIN32 + if (codepage != 0) { + SetConsoleCP(codepage); + SetConsoleOutputCP(codepage); + } +#endif + // Run frozen application - PyImport_FrozenModules = blob; + PyImport_FrozenModules = blobinfo.pointers[0]; retval = Py_FrozenMain(argc, argv); - // Free resources -#ifdef _WIN32 - free(blob); -#else - munmap(blob, size); -#endif + unmap_blob(blob); return retval; } From 409b241b40c35aee4a9304bcd59bb022e7dc90a2 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Nov 2017 22:33:49 +0100 Subject: [PATCH 160/424] Fix compile errors --- direct/src/showutil/FreezeTool.py | 2 +- dtool/src/prc/configPageManager.cxx | 4 +++- pandatool/src/deploy-stub/deploy-stub.c | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 78645acae9..cec24b7c2d 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1830,7 +1830,7 @@ class Freezer: elif data[:4] in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE', b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'): - off = self._find_symbol_macho(macho_data, symbol_name) + off = self._find_symbol_macho(data, symbol_name) if off is not None: data[off:off+len(replacement)] = replacement return True diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index 7416e0fcbe..a7fc128371 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -117,8 +117,10 @@ reload_implicit_pages() { }; #ifdef _MSC_VER const BlobInfo *blobinfo = (const BlobInfo *)GetProcAddress(GetModuleHandle(NULL), "blobinfo"); -#else +#elif defined(RTLD_SELF) const BlobInfo *blobinfo = (const BlobInfo *)dlsym(RTLD_SELF, "blobinfo"); +#else + const BlobInfo *blobinfo = (const BlobInfo *)dlsym(dlopen(NULL, RTLD_NOW), "blobinfo"); #endif if (blobinfo != nullptr && (blobinfo->version == 0 || blobinfo->num_pointers < 10)) { blobinfo = nullptr; diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 99a0e361ba..5d6eaa6139 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -362,9 +362,9 @@ int main(int argc, char *argv[]) { } #ifdef _WIN32 - if (codepage != 0) { - SetConsoleCP(codepage); - SetConsoleOutputCP(codepage); + if (blobinfo.codepage != 0) { + SetConsoleCP(blobinfo.codepage); + SetConsoleOutputCP(blobinfo.codepage); } #endif From dac08490ee7bcbebddb3656fcadeab08a81e3858 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 24 Nov 2017 23:43:15 +0100 Subject: [PATCH 161/424] deploy-ng: implement getting executable path on Linux and macOS --- pandatool/src/deploy-stub/deploy-stub.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 5d6eaa6139..c582ef0978 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -11,6 +11,10 @@ #include #endif +#ifdef __APPLE__ +#include +#endif + #include #include @@ -266,9 +270,21 @@ static void *map_blob(off_t offset, size_t size) { return NULL; } runtime = fopen(buffer, "rb"); +#elif defined(__APPLE__) + char buffer[4096]; + uint32_t bufsize = sizeof(buffer); + if (_NSGetExecutablePath(buffer, &bufsize) != 0) { + return NULL; + } + runtime = fopen(buffer, "rb"); #else - // Let's hope that it was invoked with the executable name as first arg. - runtime = fopen(argv[0], "rb"); + char buffer[4096]; + ssize_t pathlen = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1); + if (pathlen <= 0) { + perror("readlink(/proc/self/exe)"); + return NULL; + } + runtime = fopen(buffer, "rb"); #endif // Get offsets. In version 0, we read it from the end of the file. From 6fc26922de2dfef5c14be48e24045feddce8c5c6 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Fri, 24 Nov 2017 20:24:10 -0800 Subject: [PATCH 162/424] deploy-ng: Use callbacks for build_scripts --- direct/src/showutil/dist.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 4b37f88ab4..9689d33ffe 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -22,6 +22,17 @@ if 'basestring' not in globals(): basestring = str +def egg2bam(srcpath, dstpath): + dstpath = dstpath + '.bam' + subprocess.call([ + 'egg2bam', + '-o', + dstpath, + srcpath + ]) + return dstpath + + class build_apps(distutils.core.Command): description = 'build Panda3D applications' user_options = [ @@ -44,7 +55,7 @@ class build_apps(distutils.core.Command): self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] self.build_scripts= { - '.egg': ('.egg.bam', 'egg2bam -o {1} {0}'), + '.egg': egg2bam, } self.exclude_dependencies = [ # Windows @@ -371,14 +382,11 @@ class build_apps(distutils.core.Command): ext = os.path.splitext(src)[1] if not ext: ext = os.path.basename(src) - dst_root = os.path.splitext(dst)[0] if ext in self.build_scripts: - dst_ext, script = self.build_scripts[ext] - dst = dst_root + dst_ext - script = script.format(src, dst) - self.announce('using script: {}'.format(script)) - subprocess.call(script.split()) + buildscript = self.build_scripts[ext] + self.announce('running {} on src ({})'.format(buildscript.__name__, src)) + dst = self.build_scripts[ext](src, dst) else: self.announce('copying {0} -> {1}'.format(src, dst)) shutil.copyfile(src, dst) From 33fd302a8eea2fcd44a8e4bef14c82405cc2a951 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 25 Nov 2017 17:36:02 -0800 Subject: [PATCH 163/424] deploy-ng: Add files to build application out of the unit tests This will be a good way to make sure everything in the tests work under a frozen environment. NOTE: The application builds and runs, but no tests are collected. We still need to figure out how we want to handle tests. Freeze them all and modify test collection? Add py/test files to the build directory and try to run them? --- tests/main.py | 13 +++++++++++++ tests/requirements.txt | 3 +++ tests/setup.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tests/main.py create mode 100644 tests/requirements.txt create mode 100644 tests/setup.py diff --git a/tests/main.py b/tests/main.py new file mode 100644 index 0000000000..4bc1849bda --- /dev/null +++ b/tests/main.py @@ -0,0 +1,13 @@ +import _pytest +if not hasattr(_pytest, '__file__'): + import os + import _pytest._pluggy + import py + + _pytest.__file__ = os.getcwd() + _pytest._pluggy.__file__ = os.getcwd() + py.__file__ = os.getcwd() + +import pytest + +pytest.main() diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000000..b5c81dac66 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest +--pre --extra-index-url https://archive.panda3d.org/branches/deploy-ng +panda3d diff --git a/tests/setup.py b/tests/setup.py new file mode 100644 index 0000000000..cdbea42587 --- /dev/null +++ b/tests/setup.py @@ -0,0 +1,33 @@ +from setuptools import setup +import pytest + +setup( + name="panda3d-tester", + options = { + 'build_apps': { + 'gui_apps': { + 'tester': 'main.py', + }, + 'plugins': [ + 'pandagl', + 'p3openal_audio', + ], + 'include_modules': { + '*': [ + 'pkg_resources.*.*', + # TODO why does the above not get these modules too? + 'pkg_resources._vendor.packaging.*', + 'pkg_resources._vendor.packaging.version', + 'pkg_resources._vendor.packaging.specifiers', + 'pkg_resources._vendor.packaging.requirements', + ] + pytest.freeze_includes(), + }, + 'deploy_platforms': [ + 'manylinux1_x86_64', + 'macosx_10_6_x86_64', + 'win32', + 'win_amd64', + ], + } + } +) From 905b15df0101c6378b505584517b2bde61549105 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 26 Nov 2017 15:33:00 -0800 Subject: [PATCH 164/424] deploy-ng: Rename some options * include_paths -> include_patterns * exclude_paths -> exclude_patterns * deploy_platforms -> platforms --- direct/src/showutil/dist.py | 28 ++++++++++++++-------------- samples/asteroids/setup.py | 4 ++-- tests/setup.py | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 9689d33ffe..2affcb6ca2 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -46,11 +46,11 @@ class build_apps(distutils.core.Command): self.console_apps = {} self.copy_paths = [] self.rename_paths = {} - self.include_paths = [] - self.exclude_paths = [] + self.include_patterns = [] + self.exclude_patterns = [] self.include_modules = {} self.exclude_modules = {} - self.deploy_platforms = [] + self.platforms = [] self.plugins = [] self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] @@ -111,8 +111,8 @@ class build_apps(distutils.core.Command): self.rename_paths = _parse_dict(self.rename_paths) self.copy_paths = _parse_list(self.copy_paths) - self.include_paths = _parse_list(self.include_paths) - self.exclude_paths = _parse_list(self.exclude_paths) + self.include_patterns = _parse_list(self.include_patterns) + self.exclude_patterns = _parse_list(self.exclude_patterns) self.include_modules = _parse_list(self.include_modules) self.exclude_modules = _parse_list(self.exclude_modules) self.plugins = _parse_list(self.plugins) @@ -123,18 +123,18 @@ class build_apps(distutils.core.Command): else: src, _ = path assert os.path.exists(src), 'Copy path source does not exist: {}'.format(src) - assert self.deploy_platforms, 'At least one deploy platform must be defined' + assert self.platforms, 'At least one deploy platform must be defined' assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) def run(self): - if not self.deploy_platforms: + if not self.platforms: platforms = [p3d.PandaSystem.get_platform()] use_wheels = False - elif isinstance(self.deploy_platforms, basestring): - platforms = [self.deploy_platforms] + elif isinstance(self.platforms, basestring): + platforms = [self.platforms] use_wheels = True else: - platforms = self.deploy_platforms + platforms = self.platforms use_wheels = True self.announce('Building platforms: {0}'.format(','.join(platforms)), distutils.log.INFO) @@ -349,10 +349,10 @@ class build_apps(distutils.core.Command): '*.pyc', '*.py', ] - ignore_copy_list += self.exclude_paths + ignore_copy_list += self.exclude_patterns ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] - include_copy_list = [p3d.GlobPattern(i) for i in self.include_paths] + include_copy_list = [p3d.GlobPattern(i) for i in self.include_patterns] def check_pattern(src, pattern_list): # Normalize file paths across platforms @@ -673,10 +673,10 @@ class bdist_apps(distutils.core.Command): def run(self): build_cmd = self.get_finalized_command('build_apps') - if not build_cmd.deploy_platforms: + if not build_cmd.platforms: platforms = [p3d.PandaSystem.get_platform()] else: - platforms = build_cmd.deploy_platforms + platforms = build_cmd.platforms build_base = build_cmd.build_base self.run_command('build_apps') diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 31c14c485f..c592c0911f 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -4,7 +4,7 @@ setup( name="asteroids", options = { 'build_apps': { - 'include_paths': [ + 'include_patterns': [ '*.png', '*.jpg', '*.egg', @@ -16,7 +16,7 @@ setup( 'pandagl', 'p3openal_audio', ], - 'deploy_platforms': [ + 'platforms': [ 'manylinux1_x86_64', 'macosx_10_6_x86_64', 'win32', diff --git a/tests/setup.py b/tests/setup.py index cdbea42587..823044a1d4 100644 --- a/tests/setup.py +++ b/tests/setup.py @@ -22,7 +22,7 @@ setup( 'pkg_resources._vendor.packaging.requirements', ] + pytest.freeze_includes(), }, - 'deploy_platforms': [ + 'platforms': [ 'manylinux1_x86_64', 'macosx_10_6_x86_64', 'win32', From 305f39ba62a4b168c6b0d48e175cd0fc33c50007 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 26 Nov 2017 15:34:19 -0800 Subject: [PATCH 165/424] deploy-ng: Remove check for non-empty platforms list in build_apps::finalize_options() This is actually a supported use-case (we fall back to the Panda3D platform when no platforms are listed). --- direct/src/showutil/dist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2affcb6ca2..bc3687fcea 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -123,7 +123,6 @@ class build_apps(distutils.core.Command): else: src, _ = path assert os.path.exists(src), 'Copy path source does not exist: {}'.format(src) - assert self.platforms, 'At least one deploy platform must be defined' assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) def run(self): From 8f6d75e01769ebb9e9f5beeaafe3e17c990a0bed Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 26 Nov 2017 15:37:37 -0800 Subject: [PATCH 166/424] deploy-ng: Pass build_apps commands to build_scripts This allows build_scripts to access configuration. --- direct/src/showutil/dist.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index bc3687fcea..23d14824c6 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -22,7 +22,7 @@ if 'basestring' not in globals(): basestring = str -def egg2bam(srcpath, dstpath): +def egg2bam(_build_cmd, srcpath, dstpath): dstpath = dstpath + '.bam' subprocess.call([ 'egg2bam', @@ -54,7 +54,7 @@ class build_apps(distutils.core.Command): self.plugins = [] self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] - self.build_scripts= { + self.build_callbacks= { '.egg': egg2bam, } self.exclude_dependencies = [ @@ -385,7 +385,7 @@ class build_apps(distutils.core.Command): if ext in self.build_scripts: buildscript = self.build_scripts[ext] self.announce('running {} on src ({})'.format(buildscript.__name__, src)) - dst = self.build_scripts[ext](src, dst) + dst = self.build_scripts[ext](self, src, dst) else: self.announce('copying {0} -> {1}'.format(src, dst)) shutil.copyfile(src, dst) From 08e18ce802ead0c1c2087c3fe781c4f0960abed3 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 26 Nov 2017 15:50:40 -0800 Subject: [PATCH 167/424] deploy-ng: Rename build_scripts to file_handlers --- direct/src/showutil/dist.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 23d14824c6..dc120fc54c 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -54,7 +54,7 @@ class build_apps(distutils.core.Command): self.plugins = [] self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] - self.build_callbacks= { + self.file_handlers= { '.egg': egg2bam, } self.exclude_dependencies = [ @@ -382,10 +382,10 @@ class build_apps(distutils.core.Command): if not ext: ext = os.path.basename(src) - if ext in self.build_scripts: - buildscript = self.build_scripts[ext] + if ext in self.file_handlers: + buildscript = self.file_handlers[ext] self.announce('running {} on src ({})'.format(buildscript.__name__, src)) - dst = self.build_scripts[ext](self, src, dst) + dst = self.file_handlers[ext](self, src, dst) else: self.announce('copying {0} -> {1}'.format(src, dst)) shutil.copyfile(src, dst) From 0dd8f1559544fc3cbb752aa065d3b791fa2189d7 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 2 Dec 2017 12:22:18 -0800 Subject: [PATCH 168/424] deploy-ng: Remove unused build_apps.copy_paths --- direct/src/showutil/dist.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index dc120fc54c..b44b938ce5 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -44,7 +44,6 @@ class build_apps(distutils.core.Command): self.build_base = os.path.join(os.getcwd(), 'build') self.gui_apps = {} self.console_apps = {} - self.copy_paths = [] self.rename_paths = {} self.include_patterns = [] self.exclude_patterns = [] @@ -110,19 +109,12 @@ class build_apps(distutils.core.Command): self.console_apps = _parse_dict(self.console_apps) self.rename_paths = _parse_dict(self.rename_paths) - self.copy_paths = _parse_list(self.copy_paths) self.include_patterns = _parse_list(self.include_patterns) self.exclude_patterns = _parse_list(self.exclude_patterns) self.include_modules = _parse_list(self.include_modules) self.exclude_modules = _parse_list(self.exclude_modules) self.plugins = _parse_list(self.plugins) - for path in self.copy_paths: - if isinstance(path, basestring): - src = path - else: - src, _ = path - assert os.path.exists(src), 'Copy path source does not exist: {}'.format(src) assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) def run(self): From 16255d528ec1371489c0a119c7c4e4284015784b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 3 Dec 2017 17:22:41 -0800 Subject: [PATCH 169/424] deploy-ng: Lock tests app to pytest 3.2.0 pytest 3.3.0 brings in a pluggy dependency that cannot be satisfied with wheels. --- tests/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index b5c81dac66..6bb511ebbf 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,3 @@ -pytest +pytest==3.2.0 --pre --extra-index-url https://archive.panda3d.org/branches/deploy-ng panda3d From cd102e6f0940f6084480d881e0f2e81f4b72fbb3 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 3 Dec 2017 17:23:38 -0800 Subject: [PATCH 170/424] deploy-ng: Add libbz2, libz, and liblzma to the manylinux1 ignore list --- direct/src/showutil/dist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index b44b938ce5..e84530d628 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -70,6 +70,7 @@ class build_apps(distutils.core.Command): 'libdl.so.2', 'libstdc++.so.6', 'libm.so.6', 'libgcc_s.so.1', 'libpthread.so.0', 'libc.so.6', 'ld-linux-x86-64.so.2', 'libgl.so.1', 'libx11.so.6', 'libreadline.so.5', 'libncursesw.so.5', + 'libbz2.so.1', 'libz.so.1', 'liblzma.so.0', ] # We keep track of the zip files we've opened. From 6105f386c0a03cfe077431fe0b0e62646c783776 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 3 Dec 2017 17:23:58 -0800 Subject: [PATCH 171/424] deploy-ng: Add a mechanism to FreezeTool for "hidden" imports For now we have hooks for PyTest and pkg_resources. --- direct/src/showutil/FreezeTool.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index cec24b7c2d..029a9df9fc 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -59,6 +59,26 @@ builtinInitFuncs = { 'marshal': 'PyMarshal_Init', } +# These are modules that are not found normally for these modules. Add them +# to an include list so users do not have to do this manually. +try: + from pytest import freeze_includes as pytest_imports +except ImportError: + def pytest_imports(): + return [] + +hiddenImports = { + 'pytest': pytest_imports(), + 'pkg_resources': [ + 'pkg_resources.*.*', + # TODO why does the above not get these modules too? + 'pkg_resources._vendor.packaging.*', + 'pkg_resources._vendor.packaging.version', + 'pkg_resources._vendor.packaging.specifiers', + 'pkg_resources._vendor.packaging.requirements', + ], +} + # These are missing modules that we've reported already this session. reportedMissing = {} @@ -1043,6 +1063,10 @@ class Freezer: # module. pass + # Check if any new modules we found have "hidden" imports + for origName in list(self.mf.modules.keys()): + hidden = hiddenImports.get(origName, []) + # Now, any new modules we found get added to the export list. for origName in list(self.mf.modules.keys()): if origName not in origToNewName: From e4730dca00c94cb77683b61f906d18ad78bfda78 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 5 Dec 2017 19:55:18 -0800 Subject: [PATCH 172/424] deploy-ng: Error if both gui_apps and console_apps is empty --- direct/src/showutil/dist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index e84530d628..a4a0caa68b 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -117,6 +117,7 @@ class build_apps(distutils.core.Command): self.plugins = _parse_list(self.plugins) assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) + assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps' def run(self): if not self.platforms: From e504a493803fdcc5ba732b9b21a65e94514d183c Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 5 Dec 2017 21:10:56 -0800 Subject: [PATCH 173/424] deploy-ng: Add initial support for creating .app bundles for macOS The actual bundling is currently commented out in the bottom of build_apps.build_runtimes(). Generating the .app was tested from a Linux host and looks mostly correct upon a visual inspection of the contents. However, no testing was done for actually running the .app on macOS, which is why it is commented out. This was also not tested on Python 2. There are also some TODO comments that need to be addressed. --- direct/src/showutil/dist.py | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index a4a0caa68b..0e0efd5b09 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -3,6 +3,7 @@ from __future__ import print_function import collections import os import pip +import plistlib import sys import subprocess import zipfile @@ -44,6 +45,7 @@ class build_apps(distutils.core.Command): self.build_base = os.path.join(os.getcwd(), 'build') self.gui_apps = {} self.console_apps = {} + self.macos_main_app = None self.rename_paths = {} self.include_patterns = [] self.exclude_patterns = [] @@ -116,6 +118,19 @@ class build_apps(distutils.core.Command): self.exclude_modules = _parse_list(self.exclude_modules) self.plugins = _parse_list(self.plugins) + num_gui_apps = len(self.gui_apps) + num_console_apps = len(self.console_apps) + + if self.macos_main_app is None: + if num_gui_apps > 1: + assert True, 'macos_main_app must be defined if more than one gui_app is defined' + elif num_gui_apps == 1: + self.macos_main_app = list(self.gui_apps.keys())[0] + elif num_console_apps > 1: + assert True, 'macos_main_app must be defined if more than one console_app is defined with no gui_apps' + elif num_console_apps == 1: + self.macos_main_app = list(self.console_apps.keys())[0] + assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps' @@ -169,6 +184,55 @@ class build_apps(distutils.core.Command): wheelpaths = [os.path.join(whldir,i) for i in os.listdir(whldir) if platform in i] return wheelpaths + def bundle_macos_app(self, builddir): + """Bundle built runtime into a .app for macOS""" + + appname = '{}.app'.format(self.macos_main_app) + appdir = os.path.join(builddir, appname) + contentsdir = os.path.join(appdir, 'Contents') + macosdir = os.path.join(contentsdir, 'MacOS') + fwdir = os.path.join(contentsdir, 'Frameworks') + resdir = os.path.join(contentsdir, 'Resources') + + self.announce('Bundling macOS app into {}'.format(appdir), distutils.log.INFO) + + # Create initial directory structure + os.makedirs(macosdir) + os.makedirs(fwdir) + os.makedirs(resdir) + + # Move files over + for fname in os.listdir(builddir): + src = os.path.join(builddir, fname) + if appdir in src: + continue + + if fname in self.gui_apps or self.console_apps: + dst = macosdir + elif src.endswith('.so') or src.endswith('dylib'): + dst = fwdir + else: + # TODO Cg still shows up in Resources when it should be in Frameworks + dst = resdir + shutil.move(src, dst) + + # Write out Info.plist + plist = { + 'CFBundleName': appname, + 'CFBundleDisplayName': appname, #TODO use name from setup.py/cfg + 'CFBundleIdentifier': '', #TODO + 'CFBundleVersion': '0.0.0', #TODO get from setup.py + 'CFBundlePackageType': 'APPL', + 'CFBundleSignature': '', #TODO + 'CFBundleExecutable': self.macos_main_app, + } + with open(os.path.join(contentsdir, 'Info.plist'), 'wb') as f: + if hasattr(plistlib, 'dump'): + plistlib.dump(plist, f) + else: + plistlib.writePlist(plist, f) + + def build_runtimes(self, platform, use_wheels): """ Builds the distributions for the given platform. """ @@ -400,6 +464,10 @@ class build_apps(distutils.core.Command): copy_file(src, dst) + # Bundle into an .app on macOS + #if 'macosx' in platform: + # self.bundle_macos_app(builddir) + def add_dependency(self, name, target_dir, search_path, referenced_by): """ Searches for the given DLL on the search path. If it exists, copies it to the target_dir. """ From ccca2f39cb2372cf4e48540177b1b4f9db6769ae Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 16 Dec 2017 23:50:49 -0800 Subject: [PATCH 174/424] deploy-ng: Fix hidden import logic A chunk of code was left out of the previous commit. --- direct/src/showutil/FreezeTool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 029a9df9fc..9543e2d2ca 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1066,6 +1066,8 @@ class Freezer: # Check if any new modules we found have "hidden" imports for origName in list(self.mf.modules.keys()): hidden = hiddenImports.get(origName, []) + for modname in hidden: + self.__loadModule(self.ModuleDef(modname, implicit = True)) # Now, any new modules we found get added to the export list. for origName in list(self.mf.modules.keys()): From 6a630c4d6282b4e05246573e29e00e7fbdc4098d Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 16 Dec 2017 23:51:54 -0800 Subject: [PATCH 175/424] deploy-ng: Add hidden import hook for xml.etree.cElementTree --- direct/src/showutil/FreezeTool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 9543e2d2ca..abbbc38a4e 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -77,6 +77,7 @@ hiddenImports = { 'pkg_resources._vendor.packaging.specifiers', 'pkg_resources._vendor.packaging.requirements', ], + 'xml.etree.cElementTree': ['xml.etree.ElementTree'], } # These are missing modules that we've reported already this session. From 55aaa4da2be3aefad6f1793ff2c3123182717289 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 16 Dec 2017 23:57:15 -0800 Subject: [PATCH 176/424] deploy-ng: Add hidden import hook for datetime --- direct/src/showutil/FreezeTool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index abbbc38a4e..f8212c6f50 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -78,6 +78,7 @@ hiddenImports = { 'pkg_resources._vendor.packaging.requirements', ], 'xml.etree.cElementTree': ['xml.etree.ElementTree'], + 'datetime': ['_strptime'], } # These are missing modules that we've reported already this session. From 9d2e1f92cb6c5e0631d22e838ceb96bb5db45059 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 17 Dec 2017 16:29:03 +0100 Subject: [PATCH 177/424] input: fix for Windows compilation, reenable raw input --- panda/src/device/inputDevice.cxx | 21 ++++++ panda/src/device/inputDevice.h | 1 + panda/src/device/xInputDevice.cxx | 2 +- panda/src/display/graphicsWindowInputDevice.I | 10 +++ panda/src/display/graphicsWindowInputDevice.h | 1 + panda/src/event/pointerEvent.I | 35 ---------- panda/src/event/pointerEvent.h | 7 +- panda/src/event/pointerEventList.I | 70 +++++++++---------- panda/src/event/pointerEventList.cxx | 29 ++++++++ panda/src/event/pointerEventList.h | 26 +++---- panda/src/windisplay/winGraphicsWindow.cxx | 43 ++++++------ 11 files changed, 136 insertions(+), 109 deletions(-) diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 6a910de566..eae889bd1b 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -156,6 +156,27 @@ set_pointer_out_of_window(double time) { } } +/** + * Records that a relative mouse movement has taken place. + */ +void InputDevice:: +pointer_moved(double x, double y, double time) { + nassertv(_lock.debug_is_locked()); + _pointer_data._xpos += x; + _pointer_data._ypos += y; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + if (_pointer_events.is_null()) { + _pointer_events = new PointerEventList(); + } + _pointer_events->add_event(_pointer_data._in_window, + _pointer_data._xpos, + _pointer_data._ypos, + x, y, seq, time); + } +} + /** * Sets the state of the indicated button index, where true indicates down, * and false indicates up. This may generate a ButtonEvent if the button has diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 0850d4a1d3..67b6084b77 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -185,6 +185,7 @@ PUBLISHED: protected: void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); + void pointer_moved(double x, double y, double time); void set_button_state(int index, bool down); void set_control_state(int index, double state); void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 9fe3778f8c..d6832adee3 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -378,7 +378,7 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { for (int i = 0; i < 16; ++i) { if (buttons & mask) { // Set the state without triggering a button event. - _buttons[i]._state = (buttons & mask) ? S_down : S_up; + _buttons[i].state = (buttons & mask) ? S_down : S_up; } mask <<= 1; if (i == 10) { diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index ef97c1f974..c53af656f8 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -37,3 +37,13 @@ set_pointer_out_of_window(double time) { LightMutexHolder holder(_lock); InputDevice::set_pointer_out_of_window(time); } + +/** + * To be called by a particular kind of GraphicsWindow to indicate that the + * pointer has moved by the given relative amount. + */ +INLINE void GraphicsWindowInputDevice:: +pointer_moved(double x, double y, double time) { + LightMutexHolder holder(_lock); + InputDevice::pointer_moved(x, y, time); +} diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index b5b6a98a46..733f077813 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -57,6 +57,7 @@ PUBLISHED: INLINE void set_pointer_in_window(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); INLINE void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE void pointer_moved(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); private: typedef pset ButtonsHeld; diff --git a/panda/src/event/pointerEvent.I b/panda/src/event/pointerEvent.I index 1321efe477..835725bd42 100644 --- a/panda/src/event/pointerEvent.I +++ b/panda/src/event/pointerEvent.I @@ -29,41 +29,6 @@ PointerEvent() : { } -/** - * - */ -INLINE PointerEvent:: -PointerEvent(const PointerEvent ©) : - _in_window(copy._in_window), - _xpos(copy._xpos), - _ypos(copy._ypos), - _dx(copy._dx), - _dy(copy._dy), - _length(copy._length), - _direction(copy._direction), - _rotation(copy._rotation), - _sequence(copy._sequence), - _time(copy._time) -{ -} - -/** - * - */ -INLINE void PointerEvent:: -operator = (const PointerEvent ©) { - _in_window = copy._in_window; - _xpos = copy._xpos; - _ypos = copy._ypos; - _dx = copy._dx; - _dy = copy._dy; - _sequence = copy._sequence; - _length = copy._length; - _direction = copy._direction; - _rotation = copy._rotation; - _time = copy._time; -} - /** * The equality operator does not consider time significant. */ diff --git a/panda/src/event/pointerEvent.h b/panda/src/event/pointerEvent.h index f69d8d4223..138c363c46 100644 --- a/panda/src/event/pointerEvent.h +++ b/panda/src/event/pointerEvent.h @@ -25,10 +25,7 @@ class DatagramIterator; */ class EXPCL_PANDA_EVENT PointerEvent { public: - INLINE PointerEvent(); - INLINE PointerEvent(const PointerEvent ©); - INLINE void operator = (const PointerEvent ©); INLINE bool operator == (const PointerEvent &other) const; INLINE bool operator != (const PointerEvent &other) const; @@ -43,8 +40,8 @@ public: bool _in_window; int _xpos; int _ypos; - int _dx; - int _dy; + double _dx; + double _dy; double _length; double _direction; double _rotation; diff --git a/panda/src/event/pointerEventList.I b/panda/src/event/pointerEventList.I index eda98b6473..2d7f3f6d05 100644 --- a/panda/src/event/pointerEventList.I +++ b/panda/src/event/pointerEventList.I @@ -38,7 +38,7 @@ operator = (const PointerEventList ©) { /** * Returns the number of events in the list. */ -INLINE int PointerEventList:: +INLINE size_t PointerEventList:: get_num_events() const { return _events.size(); } @@ -47,90 +47,90 @@ get_num_events() const { * Get the in-window flag of the nth event. */ INLINE bool PointerEventList:: -get_in_window(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._in_window; +get_in_window(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._in_window; } /** * Get the x-coordinate of the nth event. */ INLINE int PointerEventList:: -get_xpos(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._xpos; +get_xpos(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._xpos; } /** * Get the y-coordinate of the nth event. */ INLINE int PointerEventList:: -get_ypos(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._ypos; +get_ypos(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._ypos; } /** - * Get the x-coordinate of the nth event. + * Get the x-delta of the nth event. */ -INLINE int PointerEventList:: -get_dx(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._dx; +INLINE double PointerEventList:: +get_dx(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._dx; } /** - * Get the y-coordinate of the nth event. + * Get the y-delta of the nth event. */ -INLINE int PointerEventList:: -get_dy(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._dy; +INLINE double PointerEventList:: +get_dy(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._dy; } /** * Get the length of the nth event. */ INLINE double PointerEventList:: -get_length(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._length; +get_length(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._length; } /** * Get the direction of the nth event. */ INLINE double PointerEventList:: -get_direction(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._direction; +get_direction(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._direction; } /** * Get the rotation of the nth event. */ INLINE double PointerEventList:: -get_rotation(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._rotation; +get_rotation(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._rotation; } /** * Get the sequence number of the nth event. */ INLINE int PointerEventList:: -get_sequence(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._sequence; +get_sequence(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._sequence; } /** * Get the timestamp of the nth event. */ INLINE double PointerEventList:: -get_time(int evt) const { - nassertr((evt >= 0) && (evt < (int)_events.size()), 0); - return _events[evt]._time; +get_time(size_t n) const { + nassertr(n < _events.size(), 0); + return _events[n]._time; } /** diff --git a/panda/src/event/pointerEventList.cxx b/panda/src/event/pointerEventList.cxx index fc376bc886..105c094753 100644 --- a/panda/src/event/pointerEventList.cxx +++ b/panda/src/event/pointerEventList.cxx @@ -110,6 +110,35 @@ add_event(bool in_win, int xpos, int ypos, int seq, double time) { _events.push_back(pe); } +/** + * Adds a new event to the end of the list based on the given mouse movement. + */ +void PointerEventList:: +add_event(bool in_win, int xpos, int ypos, double xdelta, double ydelta, int seq, double time) { + PointerEvent pe; + pe._in_window = in_win; + pe._xpos = xpos; + pe._ypos = ypos; + pe._dx = xdelta; + pe._dy = ydelta; + pe._sequence = seq; + pe._time = time; + pe._length = csqrt(xdelta*xdelta + ydelta*ydelta); + if (pe._length > 0.0) { + pe._direction = normalize_angle(rad_2_deg(catan2(-ydelta,xdelta))); + } else if (!_events.empty()) { + pe._direction = _events.back()._direction; + } else { + pe._direction = 0.0; + } + if (!_events.empty()) { + pe._rotation = delta_angle(_events.back()._direction, pe._direction); + } else { + pe._rotation = 0.0; + } + _events.push_back(pe); +} + /** * Returns true if the trail loops around the specified point. */ diff --git a/panda/src/event/pointerEventList.h b/panda/src/event/pointerEventList.h index 7c2400c769..552ccca32f 100644 --- a/panda/src/event/pointerEventList.h +++ b/panda/src/event/pointerEventList.h @@ -34,21 +34,23 @@ class EXPCL_PANDA_EVENT PointerEventList : public ParamValueBase { PUBLISHED: INLINE PointerEventList(); - INLINE int get_num_events() const; - INLINE bool get_in_window(int n) const; - INLINE int get_xpos(int n) const; - INLINE int get_ypos(int n) const; - INLINE int get_dx(int n) const; - INLINE int get_dy(int n) const; - INLINE int get_sequence(int n) const; - INLINE double get_length(int n) const; - INLINE double get_direction(int n) const; - INLINE double get_rotation(int n) const; - INLINE double get_time(int n) const; + INLINE size_t get_num_events() const; + INLINE bool get_in_window(size_t n) const; + INLINE int get_xpos(size_t n) const; + INLINE int get_ypos(size_t n) const; + INLINE double get_dx(size_t n) const; + INLINE double get_dy(size_t n) const; + INLINE int get_sequence(size_t n) const; + INLINE double get_length(size_t n) const; + INLINE double get_direction(size_t n) const; + INLINE double get_rotation(size_t n) const; + INLINE double get_time(size_t n) const; INLINE void clear(); INLINE void pop_front(); - void add_event(bool in_win, int xpos, int ypos, int seq, double time); + void add_event(bool in_win, int xpos, int ypos, int seq, double time); + void add_event(bool in_win, int xpos, int ypos, double xdelta, double ydelta, + int seq, double time); bool encircles(int x, int y) const; double total_turns(double sec) const; diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index f76f1baad9..c8115891a7 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -617,15 +617,16 @@ initialize_input_devices() { char *pound3 = pound2 ? strchr(pound2+1,'#') : 0; if (pound3) *pound3 = 0; for (char *p = psName; *p; p++) { - if (((*p<'a')||(*p>'z')) && ((*p<'A')||(*p>'Z')) && ((*p<'0')||(*p>'9'))) { + if (!isalnum(*p)) { *p = '_'; } } if (pound2) *pound2 = '.'; _input_device_handle[_input_devices.size()] = pRawInputDeviceList[i].hDevice; - //GraphicsWindowInputDevice device = GraphicsWindowInputDevice::pointer_only(this, psName); - //device.set_pointer_in_window(0,0); - //add_input_device(device); + + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_only(this, psName); + device->set_pointer_in_window(0, 0); + add_input_device(device); } } } @@ -2654,53 +2655,53 @@ handle_raw_input(HRAWINPUT hraw) { if (raw->header.hDevice == 0) { return; } -/* - for (int i = 1; i < (int)(_input_devices.size()); ++i) { + + for (size_t i = 1; i < _input_devices.size(); ++i) { if (_input_device_handle[i] == raw->header.hDevice) { + PT(GraphicsWindowInputDevice) input = + DCAST(GraphicsWindowInputDevice, _input_devices[i]); + int adjx = raw->data.mouse.lLastX; int adjy = raw->data.mouse.lLastY; if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - _input_devices[i].set_pointer_in_window(adjx, adjy); + input->set_pointer_in_window(adjx, adjy); } else { - //int oldx = _input_devices[i].get_raw_pointer().get_x(); - //int oldy = _input_devices[i].get_raw_pointer().get_y(); - //_input_devices[i].set_pointer_in_window(oldx + adjx, oldy + adjy); + input->pointer_moved(adjx, adjy); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) { - _input_devices[i].button_down(MouseButton::button(0), get_message_time()); + input->button_down(MouseButton::button(0), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) { - _input_devices[i].button_up(MouseButton::button(0), get_message_time()); + input->button_up(MouseButton::button(0), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) { - _input_devices[i].button_down(MouseButton::button(2), get_message_time()); + input->button_down(MouseButton::button(2), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) { - _input_devices[i].button_up(MouseButton::button(2), get_message_time()); + input->button_up(MouseButton::button(2), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) { - _input_devices[i].button_down(MouseButton::button(1), get_message_time()); + input->button_down(MouseButton::button(1), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) { - _input_devices[i].button_up(MouseButton::button(1), get_message_time()); + input->button_up(MouseButton::button(1), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) { - _input_devices[i].button_down(MouseButton::button(3), get_message_time()); + input->button_down(MouseButton::button(3), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) { - _input_devices[i].button_up(MouseButton::button(3), get_message_time()); + input->button_up(MouseButton::button(3), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) { - _input_devices[i].button_down(MouseButton::button(4), get_message_time()); + input->button_down(MouseButton::button(4), get_message_time()); } if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) { - _input_devices[i].button_up(MouseButton::button(4), get_message_time()); + input->button_up(MouseButton::button(4), get_message_time()); } } } -*/ } /** From c6401c45c1336e3929a699d208c1a5566c4be5ad Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 21 Dec 2017 12:20:19 +0100 Subject: [PATCH 178/424] deploy-ng: support macOS universal binaries --- direct/src/showutil/FreezeTool.py | 277 ++++++++++++++++++---------- dtool/src/prc/configPageManager.cxx | 6 +- 2 files changed, 180 insertions(+), 103 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index f8212c6f50..ceb85a6b11 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1648,6 +1648,8 @@ class Freezer: strings = sorted(strings, key=lambda str:-len(str)) string_offsets = {} + # Now add the strings to the pool, and collect the offsets relative to + # the beginning of the pool. for string in strings: # First check whether it's already in there; it could be part of # a longer string. @@ -1710,55 +1712,50 @@ class Freezer: # on the platform. num_pointers = 10 stub_data = bytearray(stub_file.read()) - if self._is_executable_64bit(stub_data): - header_layout = 'I', data, 4) + bitnesses = set() + ptr = 8 + for i in range(num_fat): + cputype, cpusubtype, offset, size, align = \ + struct.unpack_from('>IIIII', data, ptr) + ptr += 20 - def _replace_symbol(self, data, symbol_name, replacement): + if (cputype & 0x1000000) != 0: + bitnesses.add(64) + else: + bitnesses.add(32) + return tuple(bitnesses) + + elif data[:4] in (b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'): + # Universal binary with 64-bit offsets. + num_fat, = struct.unpack_from('>I', data, 4) + bitnesses = set() + ptr = 8 + for i in range(num_fat): + cputype, cpusubtype, offset, size, align = \ + struct.unpack_from('>QQQQQ', data, ptr) + ptr += 40 + + if (cputype & 0x1000000) != 0: + bitnesses.add(64) + else: + bitnesses.add(32) + return tuple(bitnesses) + + def _replace_symbol(self, data, symbol_name, replacement, bitness=None): """We store a custom section in the binary file containing a header - containing offsets to the binary data.""" + containing offsets to the binary data. + If bitness is set, and the binary in question is a macOS universal + binary, it only replaces for binaries with the given bitness. """ if data.startswith(b'MZ'): # A Windows PE file. @@ -1866,11 +1929,49 @@ class Freezer: elif data[:4] in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA'): # Universal binary with 32-bit offsets. - return self._replace_symbol_fat(data, b'_' + symbol_name, replacement, False) + num_fat, = struct.unpack_from('>I', data, 4) + replaced = False + ptr = 8 + for i in range(num_fat): + cputype, cpusubtype, offset, size, align = \ + struct.unpack_from('>IIIII', data, ptr) + ptr += 20 + + # Does this match the requested bitness? + if bitness is not None and ((cputype & 0x1000000) != 0) != (bitness == 64): + continue + + macho_data = data[offset:offset+size] + off = self._find_symbol_macho(macho_data, symbol_name) + if off is not None: + off += offset + data[off:off+len(replacement)] = replacement + replaced = True + + return replaced elif data[:4] in (b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'): # Universal binary with 64-bit offsets. - return self._replace_symbol_fat(data, b'_' + symbol_name, replacement, True) + num_fat, = struct.unpack_from('>I', data, 4) + replaced = False + ptr = 8 + for i in range(num_fat): + cputype, cpusubtype, offset, size, align = \ + struct.unpack_from('>QQQQQ', data, ptr) + ptr += 40 + + # Does this match the requested bitness? + if bitness is not None and ((cputype & 0x1000000) != 0) != (bitness == 64): + continue + + macho_data = data[offset:offset+size] + off = self._find_symbol_macho(macho_data, symbol_name) + if off is not None: + off += offset + data[off:off+len(replacement)] = replacement + replaced = True + + return replaced # We don't know what kind of file this is. return False @@ -1988,7 +2089,7 @@ class Freezer: symoff += nlist_size name = strings[strx : strings.find(b'\0', strx)] - if name == symbol_name: + if name == b'_' + symbol_name: # Find out in which segment this is. for vmaddr, vmsize, fileoff in segments: # Is it defined in this segment? @@ -1996,33 +2097,7 @@ class Freezer: if rel >= 0 and rel < vmsize: # Yes, so return the symbol offset. return fileoff + rel - - def _replace_symbol_fat(self, fat_data, symbol_name, replacement, is_64bit): - """ Implementation of _replace_symbol for universal binaries. """ - num_fat, = struct.unpack_from('>I', fat_data, 4) - - # After the header we get a table of executables in this fat file, - # each one with a corresponding offset into the file. - replaced = False - ptr = 8 - for i in range(num_fat): - if is_64bit: - cputype, cpusubtype, offset, size, align = \ - struct.unpack_from('>QQQQQ', fat_data, ptr) - ptr += 40 - else: - cputype, cpusubtype, offset, size, align = \ - struct.unpack_from('>IIIII', fat_data, ptr) - ptr += 20 - - macho_data = fat_data[offset:offset+size] - off = self._find_symbol_macho(macho_data, symbol_name) - if off is not None: - off += offset - fat_data[off:off+len(replacement)] = replacement - replaced = True - - return replaced + print("Could not find memory address for symbol %s" % (symbol_name)) def makeModuleDef(self, mangledName, code): result = '' diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index a7fc128371..39541212cb 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -117,8 +117,10 @@ reload_implicit_pages() { }; #ifdef _MSC_VER const BlobInfo *blobinfo = (const BlobInfo *)GetProcAddress(GetModuleHandle(NULL), "blobinfo"); -#elif defined(RTLD_SELF) - const BlobInfo *blobinfo = (const BlobInfo *)dlsym(RTLD_SELF, "blobinfo"); +#elif defined(RTLD_MAIN_ONLY) + const BlobInfo *blobinfo = (const BlobInfo *)dlsym(RTLD_MAIN_ONLY, "blobinfo"); +//#elif defined(RTLD_SELF) +// const BlobInfo *blobinfo = (const BlobInfo *)dlsym(RTLD_SELF, "blobinfo"); #else const BlobInfo *blobinfo = (const BlobInfo *)dlsym(dlopen(NULL, RTLD_NOW), "blobinfo"); #endif From f703fd51ed3838002b16e8012c89ef3f4e334da4 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 21 Dec 2017 12:24:40 +0100 Subject: [PATCH 179/424] deploy-ng: allow deploy-stub to set MAIN_DIR via blobinfo --- direct/src/showutil/FreezeTool.py | 3 +- dtool/src/interrogatedb/py_panda.cxx | 64 +++++++++++++------------ dtool/src/prc/configPageManager.cxx | 5 ++ pandatool/src/deploy-stub/deploy-stub.c | 52 ++++++++++++++------ 4 files changed, 77 insertions(+), 47 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index ceb85a6b11..1d36145661 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1710,7 +1710,7 @@ class Freezer: # Determine the format of the header and module list entries depending # on the platform. - num_pointers = 10 + num_pointers = 11 stub_data = bytearray(stub_file.read()) bitnesses = self._get_executable_bitnesses(stub_data) @@ -1804,6 +1804,7 @@ class Freezer: field_offsets.get('prc_encryption_key', 0), field_offsets.get('prc_executable_patterns', 0), field_offsets.get('prc_executable_args_envvar', 0), + field_offsets.get('main_dir', 0), 0) # Now, find the location of the 'blobinfo' symbol in the binary, diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index 5e453f207c..f0031d747b 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/dtool/src/interrogatedb/py_panda.cxx @@ -695,43 +695,45 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) { << "Python " << version << "\n"; } - // Grab the __main__ module. - PyObject *main_module = PyImport_ImportModule("__main__"); - if (main_module == NULL) { - interrogatedb_cat.warning() << "Unable to import __main__\n"; - } + if (!ExecutionEnvironment::has_environment_variable("MAIN_DIR")) { + // Grab the __main__ module. + PyObject *main_module = PyImport_ImportModule("__main__"); + if (main_module == NULL) { + interrogatedb_cat.warning() << "Unable to import __main__\n"; + } - // Extract the __file__ attribute, if present. - Filename main_dir; - PyObject *file_attr = PyObject_GetAttrString(main_module, "__file__"); - if (file_attr == NULL) { - // Must be running in the interactive interpreter. Use the CWD. - main_dir = ExecutionEnvironment::get_cwd(); - } else { + // Extract the __file__ attribute, if present. + Filename main_dir; + PyObject *file_attr = PyObject_GetAttrString(main_module, "__file__"); + if (file_attr == NULL) { + // Must be running in the interactive interpreter. Use the CWD. + main_dir = ExecutionEnvironment::get_cwd(); + } else { #if PY_MAJOR_VERSION >= 3 - Py_ssize_t length; - wchar_t *buffer = PyUnicode_AsWideCharString(file_attr, &length); - if (buffer != NULL) { - main_dir = Filename::from_os_specific_w(std::wstring(buffer, length)); - main_dir.make_absolute(); - main_dir = main_dir.get_dirname(); - PyMem_Free(buffer); - } + Py_ssize_t length; + wchar_t *buffer = PyUnicode_AsWideCharString(file_attr, &length); + if (buffer != NULL) { + main_dir = Filename::from_os_specific_w(std::wstring(buffer, length)); + main_dir.make_absolute(); + main_dir = main_dir.get_dirname(); + PyMem_Free(buffer); + } #else - char *buffer; - Py_ssize_t length; - if (PyString_AsStringAndSize(file_attr, &buffer, &length) != -1) { - main_dir = Filename::from_os_specific(std::string(buffer, length)); - main_dir.make_absolute(); - main_dir = main_dir.get_dirname(); - } + char *buffer; + Py_ssize_t length; + if (PyString_AsStringAndSize(file_attr, &buffer, &length) != -1) { + main_dir = Filename::from_os_specific(std::string(buffer, length)); + main_dir.make_absolute(); + main_dir = main_dir.get_dirname(); + } #endif - else { - interrogatedb_cat.warning() << "Invalid string for __main__.__file__\n"; + else { + interrogatedb_cat.warning() << "Invalid string for __main__.__file__\n"; + } } + ExecutionEnvironment::shadow_environment_variable("MAIN_DIR", main_dir.to_os_specific()); + PyErr_Clear(); } - ExecutionEnvironment::shadow_environment_variable("MAIN_DIR", main_dir.to_os_specific()); - PyErr_Clear(); initialized_main_dir = true; } diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index 39541212cb..2ad8f6596d 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -114,6 +114,7 @@ reload_implicit_pages() { const char *prc_encryption_key; const char *prc_executable_patterns; const char *prc_executable_args_envvar; + const char *main_dir; }; #ifdef _MSC_VER const BlobInfo *blobinfo = (const BlobInfo *)GetProcAddress(GetModuleHandle(NULL), "blobinfo"); @@ -128,6 +129,10 @@ reload_implicit_pages() { blobinfo = nullptr; } + if (blobinfo != nullptr && blobinfo->num_pointers >= 11 && blobinfo->main_dir != nullptr) { + ExecutionEnvironment::shadow_environment_variable("MAIN_DIR", blobinfo->main_dir); + } + // PRC_PATTERNS lists one or more filename templates separated by spaces. // Pull them out and store them in _prc_patterns. _prc_patterns.clear(); diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index c582ef0978..3924ba1e3f 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -2,30 +2,35 @@ #include "Python.h" #ifdef _WIN32 -#include "malloc.h" +# include "malloc.h" #else -#include +# include #endif #ifdef __FreeBSD__ -#include +# include #endif #ifdef __APPLE__ -#include +# include +# include #endif #include #include #if PY_MAJOR_VERSION >= 3 -#include +# include -#if PY_MINOR_VERSION < 5 -#define Py_DecodeLocale _Py_char2wchar -#endif +# if PY_MINOR_VERSION < 5 +# define Py_DecodeLocale _Py_char2wchar +# endif #endif +/* Leave room for future expansion. We only read pointer 0, but there are + other pointers that are being read by configPageManager.cxx. */ +#define MAX_NUM_POINTERS 24 + /* Define an exposed symbol where we store the offset to the module data. */ #ifdef _MSC_VER __declspec(dllexport) @@ -40,18 +45,15 @@ volatile struct { uint16_t codepage; uint16_t flags; uint64_t reserved; - - // Leave room for future expansion. We only read pointer 0, but there are - // other pointers that are being read by configPageManager.cxx. - void *pointers[24]; + void *pointers[MAX_NUM_POINTERS]; // The reason we initialize it to -1 is because otherwise, smart linkers may // end up putting it in the .bss section for zero-initialized data. } blobinfo = {(uint64_t)-1}; #ifdef MS_WINDOWS -#define WIN32_LEAN_AND_MEAN -#include +# define WIN32_LEAN_AND_MEAN +# include extern void PyWinFreeze_ExeInit(void); extern void PyWinFreeze_ExeTerm(void); @@ -61,7 +63,7 @@ static struct _inittab extensions[] = { }; #if PY_MAJOR_VERSION >= 3 -#define WIN_UNICODE +# define WIN_UNICODE #endif #endif @@ -91,6 +93,22 @@ static int supports_code_page(UINT cp) { } #endif +/** + * Sets the main_dir field of the blobinfo structure, but only if it wasn't + * already set. + */ +static void set_main_dir(char *main_dir) { + if (blobinfo.num_pointers >= 10) { + if (blobinfo.num_pointers == 10) { + ++blobinfo.num_pointers; + blobinfo.pointers[10] = NULL; + } + if (blobinfo.pointers[10] == NULL) { + blobinfo.pointers[10] = main_dir; + } + } +} + /* Main program */ #ifdef WIN_UNICODE @@ -356,7 +374,11 @@ int main(int argc, char *argv[]) { // Offset the pointers in the header using the base mmap address. if (blobinfo.version > 0 && blobinfo.num_pointers > 0) { uint32_t i; + assert(blobinfo.num_pointers <= MAX_NUM_POINTERS); for (i = 0; i < blobinfo.num_pointers; ++i) { + // Only offset if the pointer is non-NULL. Except for the first + // pointer, which may never be NULL and usually (but not always) + // points to the beginning of the blob. if (i == 0 || blobinfo.pointers[i] != 0) { blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob); } From 58917e6746a19151a1b42899ce1ef1baad65fb82 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 21 Dec 2017 12:29:58 +0100 Subject: [PATCH 180/424] deploy-ng: fix failure to include libpython (particularly on macOS) --- direct/src/showutil/FreezeTool.py | 4 +--- direct/src/showutil/dist.py | 27 +++++++++++++++++++++------ makepanda/makewheel.py | 26 ++++++++++++++++---------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 1d36145661..ad0798ceaf 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1619,17 +1619,15 @@ class Freezer: return target - def generateRuntimeFromStub(self, basename, stub_file, fields={}): + def generateRuntimeFromStub(self, target, stub_file, fields={}): # We must have a __main__ module to make an exe file. if not self.__writingModule('__main__'): message = "Can't generate an executable without a __main__ module." raise Exception(message) if self.platform.startswith('win'): - target = basename + '.exe' modext = '.pyd' else: - target = basename modext = '.so' # First gather up the strings and code for all the module names, and diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 0e0efd5b09..cb62ca7f77 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -280,11 +280,16 @@ class build_apps(distutils.core.Command): freezer.excludeModule(exmod) freezer.done(addStartupModules=True) + target_path = os.path.join(builddir, appname) + stub_name = 'deploy-stub' if platform.startswith('win'): if not use_console: stub_name = 'deploy-stubw' + + if platform.startswith('win'): stub_name += '.exe' + target_path += '.exe' if use_wheels: stub_file = p3dwhl.open('panda3d_tools/{0}'.format(stub_name)) @@ -293,7 +298,7 @@ class build_apps(distutils.core.Command): stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name) stub_file = open(stub_path, 'rb') - freezer.generateRuntimeFromStub(os.path.join(builddir, appname), stub_file, { + freezer.generateRuntimeFromStub(target_path, stub_file, { 'prc_data': None, 'default_prc_dir': None, 'prc_dir_envvars': None, @@ -306,6 +311,12 @@ class build_apps(distutils.core.Command): }) stub_file.close() + # Copy the dependencies. + search_path = [builddir] + if use_wheels: + search_path.append(os.path.join(p3dwhlfn, 'deploy_libs')) + self.copy_dependencies(open(target_path, 'rb'), builddir, search_path, stub_name) + freezer_extras.update(freezer.extras) freezer_modules.update(freezer.getAllModuleNames()) @@ -556,6 +567,13 @@ class build_apps(distutils.core.Command): shutil.copyfile(source_path, target_path) fp = open(target_path, 'rb') + target_dir = os.path.dirname(target_path) + base = os.path.basename(target_path) + self.copy_dependencies(fp, target_dir, search_path, base) + + def copy_dependencies(self, fp, target_dir, search_path, referenced_by): + """ Copies the dependencies of the given open file. """ + # What kind of magic does the file contain? deps = [] magic = fp.read(4) @@ -589,11 +607,8 @@ class build_apps(distutils.core.Command): deps = self._read_dependencies_fat(fp, True) # If we discovered any dependencies, recursively add those. - if deps: - target_dir = os.path.dirname(target_path) - base = os.path.basename(target_path) - for dep in deps: - self.add_dependency(dep, target_dir, search_path, base) + for dep in deps: + self.add_dependency(dep, target_dir, search_path, referenced_by) def _read_dependencies_elf(self, elf, origin, search_path): """ Having read the first 4 bytes of the ELF file, fetches the diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index c6ae353496..ae6a7183a0 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -350,19 +350,25 @@ class WheelFile(object): # Fix things like @loader_path/../lib references if sys.platform == "darwin": + deps_path = '@loader_path' loader_path = [os.path.dirname(source_path)] for dep in deps: - if '@loader_path' not in dep: - continue + if dep.endswith('/Python'): + # If this references the Python framework, change it + # to reference libpython instead. + new_dep = deps_path + '/libpython{0}.{1}.dylib'.format(*sys.version_info) + else: + if '@loader_path' not in dep: + continue - dep_path = dep.replace('@loader_path', '.') - target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep) - target_dep = self.consider_add_dependency(target_dep, dep_path, loader_path) - if not target_dep: - # It won't be included, so no use adjusting the path. - continue + dep_path = dep.replace('@loader_path', '.') + target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep) + target_dep = self.consider_add_dependency(target_dep, dep_path, loader_path) + if not target_dep: + # It won't be included, so no use adjusting the path. + continue + new_dep = os.path.join(deps_path, os.path.relpath(target_dep, os.path.dirname(target_path))) - new_dep = os.path.join('@loader_path', os.path.relpath(target_dep, os.path.dirname(target_path))) subprocess.call(["install_name_tool", "-change", dep, new_dep, temp.name]) else: subprocess.call(["strip", "-s", temp.name]) @@ -600,7 +606,7 @@ def makewheel(version, output_dir, platform=None): else: pylib_name = get_config_var('LDLIBRARY') pylib_path = os.path.join(get_config_var('LIBDIR'), pylib_name) - whl.write_file('/deploy_libs/' + pylib_name, pylib_path) + whl.write_file('deploy_libs/' + pylib_name, pylib_path) whl.close() From a512c4436659019fc35df9f7eaf33060561d47a9 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 21 Dec 2017 14:08:34 +0100 Subject: [PATCH 181/424] deploy-ng: fix Windows codec error when console codepage is 0 --- pandatool/src/deploy-stub/deploy-stub.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 3924ba1e3f..dd61ea8bd7 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -69,6 +69,10 @@ static struct _inittab extensions[] = { #if defined(_WIN32) && PY_VERSION_HEX < 0x03060000 static int supports_code_page(UINT cp) { + if (cp == 0) { + cp = GetACP(); + } + /* Shortcut, because we know that these encodings are bundled by default-- * see FreezeTool.py and Python's encodings/aliases.py */ if (cp != 0 && cp != 1252 && cp != 367 && cp != 437 && cp != 850 && cp != 819) { From 5b2f3bc9bf139028b57749610cb5ee321fc36933 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 21 Dec 2017 14:10:34 +0100 Subject: [PATCH 182/424] deploy-ng: fix issue locating blobinfo symbol on Py3+Windows --- direct/src/showutil/pefile.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/direct/src/showutil/pefile.py b/direct/src/showutil/pefile.py index 03714b82fb..4617a4e488 100755 --- a/direct/src/showutil/pefile.py +++ b/direct/src/showutil/pefile.py @@ -601,6 +601,9 @@ class PEFile(object): def get_export_address(self, symbol_name): """ Finds the virtual address for a named export symbol. """ + if isinstance(symbol_name, bytes): + symbol_name = symbol_name.decode('ascii') + start = self.exp_rva.addr expdir = expdirtab(*unpack(' Date: Thu, 21 Dec 2017 15:09:44 +0100 Subject: [PATCH 183/424] deploy-ng: fix module duplication in build dir (observed on macOS) --- direct/src/showutil/dist.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index cb62ca7f77..133b37b83e 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -340,7 +340,11 @@ class build_apps(distutils.core.Command): whl_modules_ext = '.'.join(whl_modules[0].split('.')[1:]) whl_modules = [i.split('.')[0] for i in whl_modules] - # Make sure to copy any builtins that have shared objects in the deploy libs + # Make sure to copy any builtins that have shared objects in the + # deploy libs, assuming they are not already in freezer_extras. + for mod, source_path in freezer_extras: + freezer_modules.discard(mod) + for mod in freezer_modules: if mod in whl_modules: freezer_extras.add((mod, None)) @@ -374,6 +378,7 @@ class build_apps(distutils.core.Command): if module in whl_modules: source_path = os.path.join(p3dwhlfn, 'deploy_libs/{}.{}'.format(module, whl_modules_ext))#'{0}/deploy_libs/{1}.{2}'.format(p3dwhlfn, module, whl_modules_ext) basename = os.path.basename(source_path) + #XXX should we remove python version string here too? else: continue From 2c35d7a8e9ffa43398b8c8b6964b4ed0b9c34fca Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 21 Dec 2017 15:19:50 +0100 Subject: [PATCH 184/424] deploy-ng: changes to make macOS app bundles work correctly --- direct/src/showutil/FreezeTool.py | 8 +++++-- direct/src/showutil/dist.py | 29 +++++++++++++---------- makepanda/makepanda.py | 6 +++++ makepanda/makewheel.py | 5 +++- pandatool/src/deploy-stub/deploy-stub.c | 31 +++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 15 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index ad0798ceaf..e55468c2c6 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1619,7 +1619,7 @@ class Freezer: return target - def generateRuntimeFromStub(self, target, stub_file, fields={}): + def generateRuntimeFromStub(self, target, stub_file, use_console, fields={}): # We must have a __main__ module to make an exe file. if not self.__writingModule('__main__'): message = "Can't generate an executable without a __main__ module." @@ -1700,7 +1700,11 @@ class Freezer: # trouble importing it as a builtin module. Synthesize a frozen # module that loads it dynamically. if '.' in moduleName: - code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) + if self.platform.startswith("macosx") and not use_console: + # We write the Frameworks directory to sys.path[0]. + code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext) + else: + code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) code = compile(code, moduleName, 'exec') code = marshal.dumps(code) moduleList.append((moduleName, len(pool), len(code))) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 133b37b83e..46fc3a20e0 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -33,6 +33,12 @@ def egg2bam(_build_cmd, srcpath, dstpath): ]) return dstpath +macosx_binary_magics = ( + b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE', + b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE', + b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA', + b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA') + class build_apps(distutils.core.Command): description = 'build Panda3D applications' @@ -121,15 +127,11 @@ class build_apps(distutils.core.Command): num_gui_apps = len(self.gui_apps) num_console_apps = len(self.console_apps) - if self.macos_main_app is None: + if not self.macos_main_app: if num_gui_apps > 1: - assert True, 'macos_main_app must be defined if more than one gui_app is defined' + assert False, 'macos_main_app must be defined if more than one gui_app is defined' elif num_gui_apps == 1: self.macos_main_app = list(self.gui_apps.keys())[0] - elif num_console_apps > 1: - assert True, 'macos_main_app must be defined if more than one console_app is defined with no gui_apps' - elif num_console_apps == 1: - self.macos_main_app = list(self.console_apps.keys())[0] assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps' @@ -209,10 +211,9 @@ class build_apps(distutils.core.Command): if fname in self.gui_apps or self.console_apps: dst = macosdir - elif src.endswith('.so') or src.endswith('dylib'): + elif os.path.isfile(src) and open(src, 'rb').read(4) in macosx_binary_magics: dst = fwdir else: - # TODO Cg still shows up in Resources when it should be in Frameworks dst = resdir shutil.move(src, dst) @@ -283,7 +284,7 @@ class build_apps(distutils.core.Command): target_path = os.path.join(builddir, appname) stub_name = 'deploy-stub' - if platform.startswith('win'): + if platform.startswith('win') or 'macosx' in platform: if not use_console: stub_name = 'deploy-stubw' @@ -298,7 +299,7 @@ class build_apps(distutils.core.Command): stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name) stub_file = open(stub_path, 'rb') - freezer.generateRuntimeFromStub(target_path, stub_file, { + freezer.generateRuntimeFromStub(target_path, stub_file, use_console, { 'prc_data': None, 'default_prc_dir': None, 'prc_dir_envvars': None, @@ -481,8 +482,8 @@ class build_apps(distutils.core.Command): copy_file(src, dst) # Bundle into an .app on macOS - #if 'macosx' in platform: - # self.bundle_macos_app(builddir) + if self.macos_main_app and 'macosx' in platform: + self.bundle_macos_app(builddir) def add_dependency(self, name, target_dir, search_path, referenced_by): """ Searches for the given DLL on the search path. If it exists, @@ -699,6 +700,10 @@ class build_apps(distutils.core.Command): if cmd == 0x0c: # LC_LOAD_DYLIB dylib = cmd_data[16:].decode('ascii').split('\x00', 1)[0] + if dylib.startswith('@loader_path/../Frameworks/'): + dylib = dylib.replace('@loader_path/../Frameworks/', '') + if dylib.startswith('@executable_path/../Frameworks/'): + dylib = dylib.replace('@executable_path/../Frameworks/', '') if dylib.startswith('@loader_path/'): dylib = dylib.replace('@loader_path/', '') load_dylibs.append(dylib) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index a13bd0e443..64508f3308 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6617,6 +6617,12 @@ if PkgSkip("PYTHON") == 0: TargetAdd('deploy-stubw.exe', input='deploy-stub.obj') TargetAdd('deploy-stubw.exe', input='frozen_dllmain.obj') TargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) + elif GetTarget() == 'darwin': + DefSymbol('MACOS_APP_BUNDLE', 'MACOS_APP_BUNDLE') + OPTS = OPTS + ['MACOS_APP_BUNDLE'] + TargetAdd('deploy-stubw.obj', opts=OPTS, input='deploy-stub.c') + TargetAdd('deploy-stubw.exe', input='deploy-stubw.obj') + TargetAdd('deploy-stubw.exe', opts=['MACOS_APP_BUNDLE', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) # # Generate the models directory and samples directory diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index b9b187c90f..5cf851c1ee 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -349,7 +349,10 @@ class WheelFile(object): # Fix things like @loader_path/../lib references if sys.platform == "darwin": - deps_path = '@loader_path' + if source_path.endswith('deploy-stubw'): + deps_path = '@executable_path/../Frameworks' + else: + deps_path = '@loader_path' loader_path = [os.path.dirname(source_path)] for dep in deps: if dep.endswith('/Python'): diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index dd61ea8bd7..dc05a67171 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -237,6 +237,37 @@ int Py_FrozenMain(int argc, char **argv) PySys_SetArgv(argc, argv); #endif +#ifdef MACOS_APP_BUNDLE + // Add the Frameworks directory to sys.path. + char buffer[PATH_MAX]; + uint32_t bufsize = sizeof(buffer); + if (_NSGetExecutablePath(buffer, &bufsize) != 0) { + assert(false); + return 1; + } + char resolved[PATH_MAX]; + if (!realpath(buffer, resolved)) { + perror("realpath"); + return 1; + } + const char *dir = dirname(resolved); + sprintf(buffer, "%s/../Frameworks", dir); + + PyObject *sys_path = PyList_New(1); + #if PY_MAJOR_VERSION >= 3 + PyList_SET_ITEM(sys_path, 0, PyUnicode_FromString(buffer)); + #else + PyList_SET_ITEM(sys_path, 0, PyString_FromString(buffer)); + #endif + PySys_SetObject("path", sys_path); + Py_DECREF(sys_path); + + // Now, store a path to the Resources directory into the main_dir pointer, + // for ConfigPageManager to read out and assign to MAIN_DIR. + sprintf(buffer, "%s/../Resources", dir); + set_main_dir(buffer); +#endif + n = PyImport_ImportFrozenModule("__main__"); if (n == 0) Py_FatalError("__main__ not frozen"); From ef9803b25ea77d046aaf98516df7bc12eac2a0f8 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 22 Dec 2017 14:59:52 +0100 Subject: [PATCH 185/424] Changes to support compiling input-overhaul branch on macOS --- makepanda/makepanda.py | 1 + panda/src/cocoadisplay/cocoaGraphicsWindow.h | 1 + panda/src/cocoadisplay/cocoaGraphicsWindow.mm | 58 ++++++++++--------- panda/src/display/subprocessWindow.cxx | 19 +++--- panda/src/display/subprocessWindow.h | 1 + 5 files changed, 43 insertions(+), 37 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 75261047f9..a3293aa37c 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2359,6 +2359,7 @@ def WriteConfigSettings(): dtool_config["HAVE_GLX"] = 'UNDEF' dtool_config["IS_LINUX"] = 'UNDEF' dtool_config["HAVE_VIDEO4LINUX"] = 'UNDEF' + dtool_config["PHAVE_LINUX_INPUT_H"] = 'UNDEF' dtool_config["IS_OSX"] = '1' # 10.4 had a broken ucontext implementation if int(platform.mac_ver()[0][3]) <= 4: diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.h b/panda/src/cocoadisplay/cocoaGraphicsWindow.h index 6432ecc3e8..9d31a65c14 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.h +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.h @@ -88,6 +88,7 @@ private: NSView *_view; NSUInteger _modifier_keys; CGDirectDisplayID _display; + PT(GraphicsWindowInputDevice) _input; bool _mouse_hidden; bool _context_needs_update; diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index ebc9255d63..1f29e9928e 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -65,9 +65,10 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _fullscreen_mode = NULL; _windowed_mode = NULL; - GraphicsWindowInputDevice device = + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); - add_input_device(device); + _input_devices.push_back(device.p()); + _input = move(device); CocoaGraphicsPipe *cocoa_pipe; DCAST_INTO_V(cocoa_pipe, _pipe); @@ -927,7 +928,7 @@ set_properties_now(WindowProperties &properties) { if (properties.has_cursor_hidden()) { if (properties.get_cursor_hidden() != _properties.get_cursor_hidden()) { - if (properties.get_cursor_hidden() && _input_devices[0].get_pointer().get_in_window()) { + if (properties.get_cursor_hidden() && _input->get_pointer().get_in_window()) { [NSCursor hide]; _mouse_hidden = true; } else if (_mouse_hidden) { @@ -1502,7 +1503,7 @@ handle_key_event(NSEvent *event) { // flaws are: - OS eats unmodified F11, F12, scroll lock, pause - no up // events for caps lock - no robust way to distinguish updown for modkeys if ([event type] == NSKeyUp) { - _input_devices[0].raw_button_up(raw_button); + _input->raw_button_up(raw_button); } else if ([event type] == NSFlagsChanged) { bool down = false; @@ -1524,15 +1525,15 @@ handle_key_event(NSEvent *event) { down = (modifierFlags & 0x0010); } else if (raw_button == KeyboardButton::caps_lock()) { // Emulate down-up, annoying hack! - _input_devices[0].raw_button_down(raw_button); + _input->raw_button_down(raw_button); } if (down) { - _input_devices[0].raw_button_down(raw_button); + _input->raw_button_down(raw_button); } else { - _input_devices[0].raw_button_up(raw_button); + _input->raw_button_up(raw_button); } } else if (![event isARepeat]) { - _input_devices[0].raw_button_down(raw_button); + _input->raw_button_down(raw_button); } } @@ -1556,7 +1557,7 @@ handle_key_event(NSEvent *event) { if ([event type] == NSKeyDown) { NSString *origstr = [event characters]; c = [str characterAtIndex: 0]; - _input_devices[0].keystroke(c); + _input->keystroke(c); } } @@ -1585,9 +1586,9 @@ handle_key_event(NSEvent *event) { // Let's get it off our chest. if ([event type] == NSKeyUp) { - _input_devices[0].button_up(button); + _input->button_up(button); } else { - _input_devices[0].button_down(button); + _input->button_down(button); } } @@ -1598,9 +1599,9 @@ void CocoaGraphicsWindow:: handle_modifier(NSUInteger modifierFlags, NSUInteger mask, ButtonHandle button) { if ((modifierFlags ^ _modifier_keys) & mask) { if (modifierFlags & mask) { - _input_devices[0].button_down(button); + _input->button_down(button); } else { - _input_devices[0].button_up(button); + _input->button_up(button); } } } @@ -1612,14 +1613,14 @@ handle_modifier(NSUInteger modifierFlags, NSUInteger mask, ButtonHandle button) void CocoaGraphicsWindow:: handle_mouse_button_event(int button, bool down) { if (down) { - _input_devices[0].button_down(MouseButton::button(button)); + _input->button_down(MouseButton::button(button)); #ifndef NDEBUG cocoadisplay_cat.spam() << "Mouse button " << button << " down\n"; #endif } else { - _input_devices[0].button_up(MouseButton::button(button)); + _input->button_up(MouseButton::button(button)); #ifndef NDEBUG cocoadisplay_cat.spam() @@ -1638,7 +1639,7 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) { if (absolute) { if (cocoadisplay_cat.is_spam()) { - if (in_window != _input_devices[0].get_pointer().get_in_window()) { + if (in_window != _input->get_pointer().get_in_window()) { if (in_window) { cocoadisplay_cat.spam() << "Mouse pointer entered window\n"; } else { @@ -1653,7 +1654,7 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) { } else { // We received deltas, so add it to the current mouse position. - MouseData md = _input_devices[0].get_pointer(); + MouseData md = _input->get_pointer(); nx = md.get_x() + x; ny = md.get_y() + y; } @@ -1679,8 +1680,11 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) { } } - _input_devices[0].set_pointer(in_window, nx, ny, - ClockObject::get_global_clock()->get_frame_time()); + if (in_window) { + _input->set_pointer_in_window(nx, ny); + } else { + _input->set_pointer_out_of_window(); + } if (in_window != _mouse_hidden && _properties.get_cursor_hidden()) { // Hide the cursor if the mouse enters the window, and unhide it when the @@ -1703,20 +1707,20 @@ handle_wheel_event(double x, double y) { << "Wheel delta " << x << ", " << y << "\n"; if (y > 0.0) { - _input_devices[0].button_down(MouseButton::wheel_up()); - _input_devices[0].button_up(MouseButton::wheel_up()); + _input->button_down(MouseButton::wheel_up()); + _input->button_up(MouseButton::wheel_up()); } else if (y < 0.0) { - _input_devices[0].button_down(MouseButton::wheel_down()); - _input_devices[0].button_up(MouseButton::wheel_down()); + _input->button_down(MouseButton::wheel_down()); + _input->button_up(MouseButton::wheel_down()); } // TODO: check if this is correct, I don't own a MacBook if (x > 0.0) { - _input_devices[0].button_down(MouseButton::wheel_right()); - _input_devices[0].button_up(MouseButton::wheel_right()); + _input->button_down(MouseButton::wheel_right()); + _input->button_up(MouseButton::wheel_right()); } else if (x < 0.0) { - _input_devices[0].button_down(MouseButton::wheel_left()); - _input_devices[0].button_up(MouseButton::wheel_left()); + _input->button_down(MouseButton::wheel_left()); + _input->button_up(MouseButton::wheel_left()); } } diff --git a/panda/src/display/subprocessWindow.cxx b/panda/src/display/subprocessWindow.cxx index d001f3b1f6..e643791817 100644 --- a/panda/src/display/subprocessWindow.cxx +++ b/panda/src/display/subprocessWindow.cxx @@ -35,9 +35,8 @@ SubprocessWindow(GraphicsEngine *engine, GraphicsPipe *pipe, GraphicsOutput *host) : GraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host) { - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard/mouse"); - _input_devices.push_back(device); + _input = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard/mouse"); + _input_devices.push_back(_input.p()); // This will be an offscreen buffer that we use to render the actual // contents. @@ -79,9 +78,9 @@ process_events() { while (_swbuffer->get_event(swb_event)) { // Deal with this event. if (swb_event._flags & SubprocessWindowBuffer::EF_mouse_position) { - _input_devices[0].set_pointer_in_window(swb_event._x, swb_event._y); + _input->set_pointer_in_window(swb_event._x, swb_event._y); } else if ((swb_event._flags & SubprocessWindowBuffer::EF_has_mouse) == 0) { - _input_devices[0].set_pointer_out_of_window(); + _input->set_pointer_out_of_window(); } unsigned int diff = swb_event._flags ^ _last_event_flags; @@ -108,14 +107,14 @@ process_events() { int keycode; button = translate_key(keycode, swb_event._code, swb_event._flags); if (keycode != 0 && swb_event._type != SubprocessWindowBuffer::ET_button_up) { - _input_devices[0].keystroke(keycode); + _input->keystroke(keycode); } } if (swb_event._type == SubprocessWindowBuffer::ET_button_up) { - _input_devices[0].button_up(button); + _input->button_up(button); } else if (swb_event._type == SubprocessWindowBuffer::ET_button_down) { - _input_devices[0].button_down(button); + _input->button_down(button); } } } @@ -552,9 +551,9 @@ translate_key(int &keycode, int os_code, unsigned int flags) const { void SubprocessWindow:: transition_button(unsigned int flags, ButtonHandle button) { if (flags) { - _input_devices[0].button_down(button); + _input->button_down(button); } else { - _input_devices[0].button_up(button); + _input->button_up(button); } } diff --git a/panda/src/display/subprocessWindow.h b/panda/src/display/subprocessWindow.h index 8065721f42..3d81877f91 100644 --- a/panda/src/display/subprocessWindow.h +++ b/panda/src/display/subprocessWindow.h @@ -75,6 +75,7 @@ private: private: PT(GraphicsBuffer) _buffer; PT(Texture) _texture; + PT(GraphicsWindowInputDevice) _input; int _fd; size_t _mmap_size; From 12cfc93b79f79742d66c16aac8cb8d16804ab659 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 22 Dec 2017 15:08:39 +0100 Subject: [PATCH 186/424] input: let InputDevice manage axis remapping, add C_twist axis --- panda/src/device/evdevInputDevice.cxx | 44 ++++--------- panda/src/device/evdevInputDevice.h | 6 -- panda/src/device/inputDevice.I | 4 +- panda/src/device/inputDevice.cxx | 81 +++++++++++++++++++++++- panda/src/device/inputDevice.h | 14 +++- panda/src/device/linuxJoystickDevice.cxx | 33 +++++----- panda/src/device/xInputDevice.cxx | 38 +++++++---- panda/src/vrpn/vrpnButton.cxx | 2 +- 8 files changed, 152 insertions(+), 70 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index ca2247c012..21ce8d2fa1 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -371,17 +371,10 @@ init_device() { } if (has_axes) { - AxisRange range; - range._scale = 1.0; - range._bias = 0.0; - _axis_ranges.resize(ABS_CNT, range); - - for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { + ControlAxis axis = C_none; if (i >= ABS_HAT0X) { - set_control_map(i, C_none); - // Emulate D-Pad buttons if necessary. if (i == ABS_HAT0X && emulate_dpad) { _dpad_x_axis = i; @@ -395,38 +388,25 @@ init_device() { _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); } else if (i == ABS_HAT0X) { - set_control_map(i, C_hat_x); + axis = C_hat_x; } else if (i == ABS_HAT0Y) { - set_control_map(i, C_hat_y); + axis = C_hat_y; } } else { - set_control_map(i, axis_map[i]); + axis = axis_map[i]; //cerr << "Axis " << axis_map[i] << " is mapped by the driver to " << i << "\n"; } // Check the initial value and ranges. struct input_absinfo absinfo; if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { - double factor, bias; - if (absinfo.minimum < 0) { - // Centered, eg. for sticks. - factor = 2.0 / (absinfo.maximum - absinfo.minimum); - bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum); - } else { - // 0-based, eg. for triggers. - factor = 1.0 / absinfo.maximum; - bias = 0.0; - } - // Flip Y axis to match Windows implementation. if (i == ABS_Y || i == ABS_RY) { - factor = -factor; - bias = -bias; + swap(absinfo.minimum, absinfo.maximum); } - _axis_ranges[i]._scale = factor; - _axis_ranges[i]._bias = bias; - _controls[i].state = fma(absinfo.value, factor, bias); + add_control(axis, absinfo.minimum, absinfo.maximum); + control_changed(axis, absinfo.value); if (absinfo.value != 0) { all_values_zero = false; @@ -562,13 +542,13 @@ process_events() { case EV_ABS: if (code == _dpad_x_axis) { - set_button_state(_dpad_left_button, events[i].value < 0); - set_button_state(_dpad_left_button+1, events[i].value > 0); + button_changed(_dpad_left_button, events[i].value < 0); + button_changed(_dpad_left_button+1, events[i].value > 0); } else if (code == _dpad_y_axis) { - set_button_state(_dpad_up_button, events[i].value < 0); - set_button_state(_dpad_up_button+1, events[i].value > 0); + button_changed(_dpad_up_button, events[i].value < 0); + button_changed(_dpad_up_button+1, events[i].value > 0); } - set_control_state(code, fma(events[i].value, _axis_ranges[code]._scale, _axis_ranges[code]._bias)); + control_changed(code, events[i].value); break; case EV_KEY: diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 5a604b8617..849d72ae27 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -44,12 +44,6 @@ private: int _ff_strong; int _ff_weak; - struct AxisRange { - double _scale; - double _bias; - }; - pvector _axis_ranges; - // These are used for D-pad emulation. int _dpad_x_axis; int _dpad_y_axis; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index b512b973a8..c10ba4a172 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -461,6 +461,8 @@ INLINE InputDevice::AnalogState:: AnalogState() : axis(C_none), state(0.0), - known(false) + known(false), + _scale(1.0), + _bias(0.0) { } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index eae889bd1b..a5b1f1cd45 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -114,6 +114,45 @@ get_pointer_events() { return result; } + +/** + * Called by the implementation to add a new known control. + */ +void InputDevice:: +add_control(ControlAxis axis, int minimum, int maximum, bool centered) { + AnalogState state; + state.axis = axis; + if (centered) { + // Centered, eg. for sticks. + state._scale = 2.0 / (maximum - minimum); + state._bias = (maximum + minimum) / (double)(minimum - maximum); + } else { + // 0-based, eg. for triggers. + state._scale = 1.0 / maximum; + state._bias = 0.0; + } + _controls.push_back(state); +} + +/** + * Called by the implementation to add a new known control. This version + * tries to guess whether the control is centered or not. + */ +void InputDevice:: +add_control(ControlAxis axis, int minimum, int maximum) { + bool centered = (minimum < 0) + || axis == C_left_x + || axis == C_left_y + || axis == C_right_x + || axis == C_right_y + || axis == C_x + || axis == C_y + || axis == C_wheel + || axis == C_twist + || axis == C_rudder; + add_control(axis, minimum, maximum, centered); +} + /** * Records that a mouse movement has taken place. */ @@ -165,6 +204,11 @@ pointer_moved(double x, double y, double time) { _pointer_data._xpos += x; _pointer_data._ypos += y; + if (device_cat.is_spam() && (x != 0 || y != 0)) { + device_cat.spam() + << "Pointer moved by " << x << " x " << y << "\n"; + } + if (_enable_pointer_events) { int seq = _event_sequence++; if (_pointer_events.is_null()) { @@ -184,7 +228,7 @@ pointer_moved(double x, double y, double time) { * while this call is made. */ void InputDevice:: -set_button_state(int index, bool down) { +button_changed(int index, bool down) { nassertv(_lock.debug_is_locked()); nassertv(index >= 0); if (index >= (int)_buttons.size()) { @@ -244,6 +288,34 @@ set_control_state(int index, double state) { _controls[index].known = true; } +/** + * Like set_control_state, but instead passes a raw, unscaled value. + */ +void InputDevice:: +control_changed(int index, int state) { + nassertv(_lock.debug_is_locked()); + nassertv(index >= 0); + if (index >= (int)_controls.size()) { + _controls.resize(index + 1, AnalogState()); + } + + double new_state = fma((double)state, _controls[index]._scale, _controls[index]._bias); + + if (device_cat.is_spam() && _controls[index].state != new_state) { + device_cat.spam() + << "Changed control " << index; + + if (_controls[index].axis != C_none) { + device_cat.spam(false) << " (" << _controls[index].axis << ")"; + } + + device_cat.spam(false) << " to " << new_state << " (raw value " << state << ")\n"; + } + + _controls[index].state = new_state; + _controls[index].known = true; +} + /** * Records that a tracker movement has taken place. */ @@ -467,6 +539,9 @@ operator << (ostream &out, InputDevice::DeviceClass dc) { case InputDevice::DC_hmd: out << "hmd"; break; + + case InputDevice::DC_COUNT: + break; } return out; } @@ -518,6 +593,10 @@ operator << (ostream &out, InputDevice::ControlAxis axis) { out << "throttle"; break; + case InputDevice::C_twist: + out << "twist"; + break; + case InputDevice::C_rudder: out << "rudder"; break; diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 67b6084b77..7f0e459bc3 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -108,6 +108,7 @@ PUBLISHED: C_y, C_trigger, C_throttle, + C_twist, C_rudder, C_hat_x, C_hat_y, @@ -183,11 +184,18 @@ PUBLISHED: virtual void output(ostream &out) const; protected: + // Called during the constructor to add new controls or buttons + void add_control(ControlAxis axis, int minimum, int maximum, bool centered); + void add_control(ControlAxis axis, int minimum, int maximum); + void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); + void pointer_moved(double x, double y, double time); - void set_button_state(int index, bool down); + void button_changed(int index, bool down); + void control_changed(int index, int value); void set_control_state(int index, double state); + void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); virtual void do_set_vibration(double low, double high); @@ -271,6 +279,10 @@ PUBLISHED: ControlAxis axis; double state; bool known; + + public: + double _scale; + double _bias; }; typedef pvector Controls; Controls _controls; diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index fc2e2ba7b1..8cd612f2d7 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -322,6 +322,18 @@ open_device() { break; } _controls[i].axis = axis; + + if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { + // We'd like to use 0.0 to indicate the resting position. + _controls[i]._scale = 1.0 / 65534.0; + _controls[i]._bias = 0.5; + } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { + _controls[i]._scale = 1.0 / -32767.0; + _controls[i]._bias = 0.0; + } else { + _controls[i]._scale = 1.0 / 32767.0; + _controls[i]._bias = 0.0; + } } } @@ -436,27 +448,18 @@ process_events() { int index = events[i].number; if (events[i].type & JS_EVENT_BUTTON) { - set_button_state(index, (events[i].value != 0)); + button_changed(index, (events[i].value != 0)); } else if (events[i].type & JS_EVENT_AXIS) { if (index == _dpad_x_axis) { - set_button_state(_dpad_left_button, events[i].value < -1000); - set_button_state(_dpad_left_button+1, events[i].value > 1000); + button_changed(_dpad_left_button, events[i].value < -1000); + button_changed(_dpad_left_button+1, events[i].value > 1000); } else if (index == _dpad_y_axis) { - set_button_state(_dpad_up_button, events[i].value < -1000); - set_button_state(_dpad_up_button+1, events[i].value > 1000); + button_changed(_dpad_up_button, events[i].value < -1000); + button_changed(_dpad_up_button+1, events[i].value > 1000); } - ControlAxis axis = _controls[index].axis; - - if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { - // We'd like to use 0.0 to indicate the resting position. - set_control_state(index, (events[i].value + 32767) / 65534.0); - } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { - set_control_state(index, events[i].value / -32767.0); - } else { - set_control_state(index, events[i].value / 32767.0); - } + control_changed(index, events[i].value); } } diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index d6832adee3..04d88996f5 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -290,6 +290,18 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { break; } + _controls[0]._scale = 1.0 / 255.0; + _controls[1]._scale = 1.0 / 255.0; + _controls[2]._scale = 1.0 / 32767.5; + _controls[3]._scale = 1.0 / 32767.5; + _controls[4]._scale = 1.0 / 32767.5; + _controls[5]._scale = 1.0 / 32767.5; + + _controls[2]._bias = 0.5 / 32767.5; + _controls[3]._bias = 0.5 / 32767.5; + _controls[4]._bias = 0.5 / 32767.5; + _controls[5]._bias = 0.5 / 32767.5; + if (caps.Flags & XINPUT_CAPS_NO_NAVIGATION) { set_button_map(0, ButtonHandle::none()); set_button_map(1, ButtonHandle::none()); @@ -387,12 +399,12 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { } } - set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); - set_control_state(1, state.Gamepad.bRightTrigger / 255.0); - set_control_state(2, state.Gamepad.sThumbLX / 32767.0); - set_control_state(3, state.Gamepad.sThumbLY / 32767.0); - set_control_state(4, state.Gamepad.sThumbRX / 32767.0); - set_control_state(5, state.Gamepad.sThumbRY / 32767.0); + control_changed(0, state.Gamepad.bLeftTrigger); + control_changed(1, state.Gamepad.bRightTrigger); + control_changed(2, state.Gamepad.sThumbLX); + control_changed(3, state.Gamepad.sThumbLY); + control_changed(4, state.Gamepad.sThumbRX); + control_changed(5, state.Gamepad.sThumbRY); _last_buttons = buttons; _last_packet = state.dwPacketNumber; @@ -453,7 +465,7 @@ do_poll() { WORD mask = 1; for (int i = 0; i < 16; ++i) { if (changed_buttons & mask) { - set_button_state(i, (state.Gamepad.wButtons & mask) != 0); + button_changed(i, (state.Gamepad.wButtons & mask) != 0); } mask <<= 1; if (i == 10) { @@ -462,12 +474,12 @@ do_poll() { } } - set_control_state(0, state.Gamepad.bLeftTrigger / 255.0); - set_control_state(1, state.Gamepad.bRightTrigger / 255.0); - set_control_state(2, (state.Gamepad.sThumbLX + 0.5) / 32767.5); - set_control_state(3, (state.Gamepad.sThumbLY + 0.5) / 32767.5); - set_control_state(4, (state.Gamepad.sThumbRX + 0.5) / 32767.5); - set_control_state(5, (state.Gamepad.sThumbRY + 0.5) / 32767.5); + control_changed(0, state.Gamepad.bLeftTrigger); + control_changed(1, state.Gamepad.bRightTrigger); + control_changed(2, state.Gamepad.sThumbLX); + control_changed(3, state.Gamepad.sThumbLY); + control_changed(4, state.Gamepad.sThumbRX); + control_changed(5, state.Gamepad.sThumbRY); _last_buttons = state.Gamepad.wButtons; _last_packet = state.dwPacketNumber; diff --git a/panda/src/vrpn/vrpnButton.cxx b/panda/src/vrpn/vrpnButton.cxx index c1505619d8..60aff640e7 100644 --- a/panda/src/vrpn/vrpnButton.cxx +++ b/panda/src/vrpn/vrpnButton.cxx @@ -103,6 +103,6 @@ vrpn_button_callback(void *userdata, const vrpn_BUTTONCB info) { Devices::iterator di; for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnButtonDevice *device = (*di); - device->set_button_state(info.button, info.state != 0); + device->button_changed(info.button, info.state != 0); } } From 920f1b75e35c4ba185c75ed97964359de75b6f84 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 22 Dec 2017 15:32:03 +0100 Subject: [PATCH 187/424] input: support macOS gamepads, joysticks, raw keyboards/mice I tested gamepads with a wireless 360 controller and a wired Xbox One controller using the 360Controller driver. I tested the joystick support using a T.Flight Hotas X. --- panda/src/device/inputDeviceManager.cxx | 66 +- panda/src/device/inputDeviceManager.h | 10 + panda/src/device/ioKitInputDevice.cxx | 772 +++++++++++++++++++++++ panda/src/device/ioKitInputDevice.h | 55 ++ panda/src/device/p3device_composite1.cxx | 5 +- 5 files changed, 905 insertions(+), 3 deletions(-) create mode 100644 panda/src/device/ioKitInputDevice.cxx create mode 100644 panda/src/device/ioKitInputDevice.h diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index edcc2a7903..d7f6a04955 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -12,6 +12,7 @@ */ #include "inputDeviceManager.h" +#include "ioKitInputDevice.h" #include "linuxJoystickDevice.h" #include "throw_event.h" @@ -108,6 +109,48 @@ InputDeviceManager() : _connected_devices.add_device(&_xinput_device3); } } +#elif defined(__APPLE__) +InputDeviceManager:: +InputDeviceManager() { + _hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (!_hid_manager) { + device_cat.error() + << "Failed to create an IOHIDManager.\n"; + return; + } + + // The types of devices we're interested in. + int page = kHIDPage_GenericDesktop; + int usages[] = {kHIDUsage_GD_GamePad, + kHIDUsage_GD_Joystick, + kHIDUsage_GD_Mouse, + kHIDUsage_GD_Keyboard, + kHIDUsage_GD_MultiAxisController, 0}; + int *usage = usages; + + // This giant mess is necessary to create an array of match dictionaries + // that will match the devices we're interested in. + CFMutableArrayRef match = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + nassertv(match); + while (*usage) { + CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFNumberRef page_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), page_ref); + CFRelease(page_ref); + CFNumberRef usage_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, usage); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), usage_ref); + CFRelease(usage_ref); + CFArrayAppendValue(match, dict); + CFRelease(dict); + ++usage; + } + IOHIDManagerSetDeviceMatchingMultiple(_hid_manager, match); + CFRelease(match); + + IOHIDManagerRegisterDeviceMatchingCallback(_hid_manager, on_match_device, this); + IOHIDManagerScheduleWithRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); + IOHIDManagerOpen(_hid_manager, kIOHIDOptionsTypeNone); +} #else InputDeviceManager:: InputDeviceManager() { @@ -124,6 +167,10 @@ InputDeviceManager:: close(_inotify_fd); _inotify_fd = -1; } +#elif defined(__APPLE__) + IOHIDManagerUnscheduleFromRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); + IOHIDManagerClose(_hid_manager, kIOHIDOptionsTypeNone); + CFRelease(_hid_manager); #endif } @@ -318,7 +365,8 @@ remove_device(InputDevice *device) { } /** - * Polls the system to see if there are any new devices. + * Polls the system to see if there are any new devices. In some + * implementations this is a no-op. */ void InputDeviceManager:: update() { @@ -426,3 +474,19 @@ update() { } #endif } + +#if defined(__APPLE__) && !defined(CPPPARSER) +void InputDeviceManager:: +on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) { + InputDeviceManager *mgr = (InputDeviceManager *)ctx; + nassertv(mgr != nullptr); + nassertv(device); + + PT(InputDevice) input_device = new IOKitInputDevice(device); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered input device " << *input_device << "\n"; + } + mgr->add_device(input_device); +} +#endif diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 9596a841d2..a262b60d28 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -23,6 +23,10 @@ #include "xinputDevice.h" #endif +#ifdef __APPLE__ +#include +#endif + /** * This class keeps track of all the devices on a system, and sends out events * when a device has been hot-plugged. @@ -71,6 +75,12 @@ private: double _last_detection; #endif +#if defined(__APPLE__) && !defined(CPPPARSER) + IOHIDManagerRef _hid_manager; + + static void on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device); +#endif + InputDeviceSet _connected_devices; static InputDeviceManager *_global_ptr; diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx new file mode 100644 index 0000000000..ec0522b1a0 --- /dev/null +++ b/panda/src/device/ioKitInputDevice.cxx @@ -0,0 +1,772 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ioKitInputDevice.cxx + * @author rdb + * @date 2017-12-21 + */ + +#include "ioKitInputDevice.h" + +#if defined(__APPLE__) && !defined(CPPPARSER) + +#include + +#include "keyboardButton.h" +#include "gamepadButton.h" +#include "mouseButton.h" + +static void removal_callback(void *ctx, IOReturn result, void *sender) { + IOKitInputDevice *input_device = (IOKitInputDevice *)ctx; + nassertv(input_device != nullptr); + input_device->on_remove(); +} + +/** + * Protected constructor. + */ +IOKitInputDevice:: +IOKitInputDevice(IOHIDDeviceRef device) : + _device(device), + _hat_element(nullptr), + _pointer_x(nullptr), + _pointer_y(nullptr), + _scroll_wheel(nullptr), + _pointer_x_timestamp(0), + _pointer_y_timestamp(0), + _scroll_wheel_timestamp(0) { + nassertv(device); + + char buffer[4096]; + + CFStringRef name = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); + if (name) { + CFStringGetCString(name, buffer, sizeof(buffer), kCFStringEncodingUTF8); + _name = buffer; + } + + CFStringRef mfg = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey)); + if (mfg) { + CFStringGetCString(mfg, buffer, sizeof(buffer), kCFStringEncodingUTF8); + _manufacturer = buffer; + } + + CFStringRef serial = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDSerialNumberKey)); + if (serial) { + CFStringGetCString(serial, buffer, sizeof(buffer), kCFStringEncodingUTF8); + _serial_number = buffer; + } + + CFNumberRef vendor = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)); + if (vendor) { + int32_t value = 0; + CFNumberGetValue(vendor, kCFNumberSInt32Type, &value); + _vendor_id = (unsigned short)value; + } + + CFNumberRef product = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)); + if (product) { + int32_t value = 0; + CFNumberGetValue(product, kCFNumberSInt32Type, &value); + _product_id = (unsigned short)value; + } + + if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse)) { + _device_class = DC_mouse; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) { + _device_class = DC_keyboard; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad)) { + _device_class = DC_gamepad; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_Sim_FlightStick)) { + _device_class = DC_flight_stick; + } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_GD_Joystick)) { + _device_class = DC_flight_stick; + } else if (_vendor_id == 0x044f && _product_id == 0xb108) { + // T.Flight Hotas X + _device_class = DC_flight_stick; + } + + CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, 0); + CFIndex count = CFArrayGetCount(elements); + for (CFIndex i = 0; i < count; ++i) { + IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i); + parse_element(element); + } + CFRelease(elements); + + if (_hat_element != nullptr) { + _hat_x_axis = _controls.size(); + add_control(C_hat_x, -1, 1, true); + add_control(C_hat_y, -1, 1, true); + } + + if (_pointer_x != nullptr && _pointer_y != nullptr) { + _flags |= IDF_has_pointer; + } + + _is_connected = true; + IOHIDDeviceRegisterRemovalCallback(device, removal_callback, this); +} + +/** + * + */ +IOKitInputDevice:: +~IOKitInputDevice() { +} + +/** + * The nonstatic version of on_remove_device. + */ +void IOKitInputDevice:: +on_remove() { + { + LightMutexHolder holder(_lock); + if (!_is_connected) { + return; + } + _is_connected = false; + } + + if (device_cat.is_debug()) { + device_cat.debug() + << "Removed input device " << *this << "\n"; + } + + IOHIDDeviceClose(_device, kIOHIDOptionsTypeNone); + + InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); + nassertv(mgr != nullptr); + mgr->remove_device(this); +} + +/** + * + */ +void IOKitInputDevice:: +parse_element(IOHIDElementRef element) { + ButtonHandle handle = ButtonHandle::none(); + ControlAxis axis = C_none; + uint32_t page = IOHIDElementGetUsagePage(element); + uint32_t usage = IOHIDElementGetUsage(element); + + switch (IOHIDElementGetType(element)) { + case kIOHIDElementTypeInput_Misc: + switch (page) { + case kHIDPage_GenericDesktop: + switch (usage) { + case kHIDUsage_GD_X: + if (_device_class == DC_gamepad) { + axis = C_left_x; + } else if (_device_class == DC_mouse) { + _pointer_x = element; + return; + } else { + axis = C_x; + } + break; + case kHIDUsage_GD_Y: + if (_device_class == DC_gamepad) { + axis = C_left_y; + } else if (_device_class == DC_mouse) { + _pointer_y = element; + return; + } else { + axis = C_y; + } + break; + case kHIDUsage_GD_Z: + if (_device_class == DC_gamepad) { + axis = C_left_trigger; + } else { + axis = C_throttle; + } + break; + case kHIDUsage_GD_Rx: + axis = C_right_x; + break; + case kHIDUsage_GD_Ry: + axis = C_right_y; + break; + case kHIDUsage_GD_Rz: + if (_device_class == DC_gamepad) { + axis = C_right_trigger; + } else { + axis = C_twist; + } + break; + case kHIDUsage_GD_Slider: + axis = C_rudder; + break; + case kHIDUsage_GD_Dial: + break; + case kHIDUsage_GD_Wheel: + _scroll_wheel = element; + return; + case kHIDUsage_GD_Hatswitch: + _hat_element = element; + return; + case kHIDUsage_GD_DPadUp: + handle = GamepadButton::dpad_up(); + break; + case kHIDUsage_GD_DPadDown: + handle = GamepadButton::dpad_down(); + break; + case kHIDUsage_GD_DPadRight: + handle = GamepadButton::dpad_right(); + break; + case kHIDUsage_GD_DPadLeft: + handle = GamepadButton::dpad_left(); + break; + case 0xffffffffu: + return; + default: + break; + } + break; + + case kHIDPage_Simulation: + switch (usage) { + case kHIDUsage_Sim_Rudder: + axis = C_rudder; + break; + case kHIDUsage_Sim_Throttle: + axis = C_throttle; + break; + case kHIDUsage_Sim_Accelerator: + axis = C_accelerator; + break; + case kHIDUsage_Sim_Brake: + axis = C_brake; + break; + } + break; + } + if (axis != C_none) { + int min = IOHIDElementGetLogicalMin(element); + int max = IOHIDElementGetLogicalMax(element); + if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { + // T.Flight Hotas X throttle is reversed and can go backwards. + add_control(axis, max, min, true); + } else if (axis == C_y || axis == C_left_y || axis == C_right_y) { + // We'd like to reverse the Y axis to match the XInput behavior. + add_control(axis, max, min); + } else { + add_control(axis, min, max); + } + + _analog_elements.push_back(element); + } + break; + + case kIOHIDElementTypeInput_Button: + switch (page) { + case kHIDPage_GenericDesktop: + switch (usage) { + case kHIDUsage_GD_DPadUp: + handle = GamepadButton::dpad_up(); + break; + case kHIDUsage_GD_DPadDown: + handle = GamepadButton::dpad_down(); + break; + case kHIDUsage_GD_DPadRight: + handle = GamepadButton::dpad_right(); + break; + case kHIDUsage_GD_DPadLeft: + handle = GamepadButton::dpad_left(); + break; + default: + break; + } + break; + + case kHIDPage_KeyboardOrKeypad: + switch (usage) { + case kHIDUsage_KeyboardA: + case kHIDUsage_KeyboardB: + case kHIDUsage_KeyboardC: + case kHIDUsage_KeyboardD: + case kHIDUsage_KeyboardE: + case kHIDUsage_KeyboardF: + case kHIDUsage_KeyboardG: + case kHIDUsage_KeyboardH: + case kHIDUsage_KeyboardI: + case kHIDUsage_KeyboardJ: + case kHIDUsage_KeyboardK: + case kHIDUsage_KeyboardL: + case kHIDUsage_KeyboardM: + case kHIDUsage_KeyboardN: + case kHIDUsage_KeyboardO: + case kHIDUsage_KeyboardP: + case kHIDUsage_KeyboardQ: + case kHIDUsage_KeyboardR: + case kHIDUsage_KeyboardS: + case kHIDUsage_KeyboardT: + case kHIDUsage_KeyboardU: + case kHIDUsage_KeyboardV: + case kHIDUsage_KeyboardW: + case kHIDUsage_KeyboardX: + case kHIDUsage_KeyboardY: + case kHIDUsage_KeyboardZ: + handle = KeyboardButton::ascii_key('a' + (usage - kHIDUsage_KeyboardA)); + break; + case kHIDUsage_Keyboard1: + handle = KeyboardButton::ascii_key('1'); + break; + case kHIDUsage_Keyboard2: + handle = KeyboardButton::ascii_key('2'); + break; + case kHIDUsage_Keyboard3: + handle = KeyboardButton::ascii_key('3'); + break; + case kHIDUsage_Keyboard4: + handle = KeyboardButton::ascii_key('4'); + break; + case kHIDUsage_Keyboard5: + handle = KeyboardButton::ascii_key('5'); + break; + case kHIDUsage_Keyboard6: + handle = KeyboardButton::ascii_key('6'); + break; + case kHIDUsage_Keyboard7: + handle = KeyboardButton::ascii_key('7'); + break; + case kHIDUsage_Keyboard8: + handle = KeyboardButton::ascii_key('8'); + break; + case kHIDUsage_Keyboard9: + handle = KeyboardButton::ascii_key('9'); + break; + case kHIDUsage_Keyboard0: + handle = KeyboardButton::ascii_key('0'); + break; + case kHIDUsage_KeyboardReturnOrEnter: + handle = KeyboardButton::enter(); + break; + case kHIDUsage_KeyboardEscape: + handle = KeyboardButton::escape(); + break; + case kHIDUsage_KeyboardDeleteOrBackspace: + handle = KeyboardButton::backspace(); + break; + case kHIDUsage_KeyboardTab: + handle = KeyboardButton::tab(); + break; + case kHIDUsage_KeyboardSpacebar: + handle = KeyboardButton::ascii_key(' '); + break; + case kHIDUsage_KeyboardHyphen: + handle = KeyboardButton::ascii_key('-'); + break; + case kHIDUsage_KeyboardEqualSign: + handle = KeyboardButton::ascii_key('='); + break; + case kHIDUsage_KeyboardOpenBracket: + handle = KeyboardButton::ascii_key('['); + break; + case kHIDUsage_KeyboardCloseBracket: + handle = KeyboardButton::ascii_key(']'); + break; + case kHIDUsage_KeyboardBackslash: + handle = KeyboardButton::ascii_key('\\'); + break; + case kHIDUsage_KeyboardNonUSPound: + handle = KeyboardButton::ascii_key('$'); + break; + case kHIDUsage_KeyboardSemicolon: + handle = KeyboardButton::ascii_key(';'); + break; + case kHIDUsage_KeyboardQuote: + handle = KeyboardButton::ascii_key('\''); + break; + case kHIDUsage_KeyboardGraveAccentAndTilde: + handle = KeyboardButton::ascii_key('`'); + break; + case kHIDUsage_KeyboardComma: + handle = KeyboardButton::ascii_key(','); + break; + case kHIDUsage_KeyboardPeriod: + handle = KeyboardButton::ascii_key('.'); + break; + case kHIDUsage_KeyboardSlash: + handle = KeyboardButton::ascii_key('/'); + break; + case kHIDUsage_KeyboardCapsLock: + handle = KeyboardButton::caps_lock(); + break; + case kHIDUsage_KeyboardF1: + handle = KeyboardButton::f1(); + break; + case kHIDUsage_KeyboardF2: + handle = KeyboardButton::f2(); + break; + case kHIDUsage_KeyboardF3: + handle = KeyboardButton::f3(); + break; + case kHIDUsage_KeyboardF4: + handle = KeyboardButton::f4(); + break; + case kHIDUsage_KeyboardF5: + handle = KeyboardButton::f5(); + break; + case kHIDUsage_KeyboardF6: + handle = KeyboardButton::f6(); + break; + case kHIDUsage_KeyboardF7: + handle = KeyboardButton::f7(); + break; + case kHIDUsage_KeyboardF8: + handle = KeyboardButton::f8(); + break; + case kHIDUsage_KeyboardF9: + handle = KeyboardButton::f9(); + break; + case kHIDUsage_KeyboardF10: + handle = KeyboardButton::f10(); + break; + case kHIDUsage_KeyboardF11: + handle = KeyboardButton::f11(); + break; + case kHIDUsage_KeyboardF12: + handle = KeyboardButton::f12(); + break; + case kHIDUsage_KeyboardPrintScreen: + handle = KeyboardButton::print_screen(); + break; + case kHIDUsage_KeyboardScrollLock: + handle = KeyboardButton::scroll_lock(); + break; + case kHIDUsage_KeyboardPause: + handle = KeyboardButton::pause(); + break; + case kHIDUsage_KeyboardInsert: + handle = KeyboardButton::insert(); + break; + case kHIDUsage_KeyboardHome: + handle = KeyboardButton::home(); + break; + case kHIDUsage_KeyboardPageUp: + handle = KeyboardButton::page_up(); + break; + case kHIDUsage_KeyboardDeleteForward: + handle = KeyboardButton::del(); + break; + case kHIDUsage_KeyboardEnd: + handle = KeyboardButton::end(); + break; + case kHIDUsage_KeyboardPageDown: + handle = KeyboardButton::page_down(); + break; + case kHIDUsage_KeyboardRightArrow: + handle = KeyboardButton::right(); + break; + case kHIDUsage_KeyboardLeftArrow: + handle = KeyboardButton::left(); + break; + case kHIDUsage_KeyboardDownArrow: + handle = KeyboardButton::down(); + break; + case kHIDUsage_KeyboardUpArrow: + handle = KeyboardButton::up(); + break; + case kHIDUsage_KeypadNumLock: + handle = KeyboardButton::num_lock(); + break; + case kHIDUsage_KeypadSlash: + handle = KeyboardButton::ascii_key('/'); + break; + case kHIDUsage_KeypadAsterisk: + handle = KeyboardButton::ascii_key('*'); + break; + case kHIDUsage_KeypadHyphen: + handle = KeyboardButton::ascii_key('-'); + break; + case kHIDUsage_KeypadPlus: + handle = KeyboardButton::ascii_key('+'); + break; + case kHIDUsage_KeypadEnter: + handle = KeyboardButton::enter(); + break; + case kHIDUsage_Keypad1: + handle = KeyboardButton::ascii_key('1'); + break; + case kHIDUsage_Keypad2: + handle = KeyboardButton::ascii_key('2'); + break; + case kHIDUsage_Keypad3: + handle = KeyboardButton::ascii_key('3'); + break; + case kHIDUsage_Keypad4: + handle = KeyboardButton::ascii_key('4'); + break; + case kHIDUsage_Keypad5: + handle = KeyboardButton::ascii_key('5'); + break; + case kHIDUsage_Keypad6: + handle = KeyboardButton::ascii_key('6'); + break; + case kHIDUsage_Keypad7: + handle = KeyboardButton::ascii_key('7'); + break; + case kHIDUsage_Keypad8: + handle = KeyboardButton::ascii_key('8'); + break; + case kHIDUsage_Keypad9: + handle = KeyboardButton::ascii_key('9'); + break; + case kHIDUsage_Keypad0: + handle = KeyboardButton::ascii_key('0'); + break; + case kHIDUsage_KeypadPeriod: + handle = KeyboardButton::ascii_key('.'); + break; + case kHIDUsage_KeyboardNonUSBackslash: + handle = KeyboardButton::ascii_key('\\'); + break; + case kHIDUsage_KeypadEqualSign: + handle = KeyboardButton::ascii_key('='); + break; + case kHIDUsage_KeyboardF13: + handle = KeyboardButton::f13(); + break; + case kHIDUsage_KeyboardF14: + handle = KeyboardButton::f14(); + break; + case kHIDUsage_KeyboardF15: + handle = KeyboardButton::f15(); + break; + case kHIDUsage_KeyboardF16: + handle = KeyboardButton::f16(); + break; + case kHIDUsage_KeyboardExecute: + break; + case kHIDUsage_KeyboardHelp: + handle = KeyboardButton::help(); + break; + case kHIDUsage_KeyboardMenu: + handle = KeyboardButton::menu(); + break; + case kHIDUsage_KeypadComma: + handle = KeyboardButton::ascii_key(','); + break; + case kHIDUsage_KeypadEqualSignAS400: + handle = KeyboardButton::ascii_key('='); + break; + case kHIDUsage_KeyboardReturn: + handle = KeyboardButton::enter(); + break; + case kHIDUsage_KeyboardLeftControl: + handle = KeyboardButton::lcontrol(); + break; + case kHIDUsage_KeyboardLeftShift: + handle = KeyboardButton::lshift(); + break; + case kHIDUsage_KeyboardLeftAlt: + handle = KeyboardButton::lalt(); + break; + case kHIDUsage_KeyboardLeftGUI: + handle = KeyboardButton::lmeta(); + break; + case kHIDUsage_KeyboardRightControl: + handle = KeyboardButton::rcontrol(); + break; + case kHIDUsage_KeyboardRightShift: + handle = KeyboardButton::rshift(); + break; + case kHIDUsage_KeyboardRightAlt: + handle = KeyboardButton::ralt(); + break; + case kHIDUsage_KeyboardRightGUI: + handle = KeyboardButton::rmeta(); + break; + default: + break; + } + break; + + case kHIDPage_Button: + if (_device_class == DC_gamepad) { + // These seem to be the button mappings exposed by the 360Controller + // driver I don't know if other drivers do the same thing at all. + static const ButtonHandle gamepad_buttons[] = { + ButtonHandle::none(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::lstick(), + GamepadButton::rstick(), + GamepadButton::start(), + GamepadButton::back(), + GamepadButton::guide(), + GamepadButton::dpad_up(), + GamepadButton::dpad_down(), + GamepadButton::dpad_left(), + GamepadButton::dpad_right(), + }; + if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + handle = gamepad_buttons[usage]; + } + } else if (_device_class == DC_mouse) { + // In Panda, wheel and right button are flipped around... + int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); + handle = MouseButton::button(button); + } + break; + } + _buttons.push_back(ButtonState(handle)); + _button_elements.push_back(element); + break; + + case kIOHIDElementTypeInput_Axis: + break; + + case kIOHIDElementTypeInput_ScanCodes: + break; + + case kIOHIDElementTypeOutput: + break; + + case kIOHIDElementTypeFeature: + break; + + case kIOHIDElementTypeCollection: + { + // This doesn't seem to be necessary and instead leads to duplication of + // axes and buttons. + /* + CFArrayRef children = IOHIDElementGetChildren(element); + CFIndex count = CFArrayGetCount(children); + for (CFIndex i = 0; i < count; ++i) { + IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(children, i); + parse_element(element, depth + 2); + }*/ + } + break; + } +} + +/** + * Polls the input device for new activity, to ensure it contains the latest + * events. This will only have any effect for some types of input devices; + * others may be updated automatically, and this method will be a no-op. + */ +void IOKitInputDevice:: +do_poll() { + for (size_t i = 0; i < _button_elements.size(); ++i) { + IOHIDValueRef value_ref; + if (!_button_elements[i]) continue; + if (IOHIDDeviceGetValue(_device, _button_elements[i], &value_ref) == kIOReturnSuccess) { + int value = IOHIDValueGetIntegerValue(value_ref); + button_changed(i, value != 0); + } + } + + for (size_t i = 0; i < _analog_elements.size(); ++i) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _analog_elements[i], &value_ref) == kIOReturnSuccess) { + int value = IOHIDValueGetIntegerValue(value_ref); + control_changed(i, value); + } + } + + if (_hat_element != nullptr) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _hat_element, &value_ref) == kIOReturnSuccess) { + int value = IOHIDValueGetIntegerValue(value_ref); + int x = 0; + int y = 0; + switch (value) { + case 0: + x = 0; + y = -1; + break; + case 1: + x = 1; + y = -1; + break; + case 2: + x = 1; + y = 0; + break; + case 3: + x = 1; + y = 1; + break; + case 4: + x = 0; + y = 1; + break; + case 5: + x = -1; + y = 1; + break; + case 6: + x = -1; + y = 0; + break; + case 7: + x = -1; + y = -1; + break; + default: + break; + } + control_changed(_hat_x_axis, x); + control_changed(_hat_x_axis + 1, y); + } + } + + int x = 0, y = 0; + if (_pointer_x != nullptr) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _pointer_x, &value_ref) == kIOReturnSuccess) { + uint64_t timestamp = IOHIDValueGetTimeStamp(value_ref); + if (timestamp != _pointer_x_timestamp) { + x = IOHIDValueGetIntegerValue(value_ref); + _pointer_x_timestamp = timestamp; + } + } + } + if (_pointer_y != nullptr) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _pointer_y, &value_ref) == kIOReturnSuccess) { + uint64_t timestamp = IOHIDValueGetTimeStamp(value_ref); + if (timestamp != _pointer_y_timestamp) { + y = IOHIDValueGetIntegerValue(value_ref); + _pointer_y_timestamp = timestamp; + } + } + } + if (x != 0 || y != 0) { + pointer_moved(x, y, ClockObject::get_global_clock()->get_frame_time()); + } + + // Do we have a scroll wheel axis? + if (_scroll_wheel != nullptr) { + IOHIDValueRef value_ref; + if (IOHIDDeviceGetValue(_device, _scroll_wheel, &value_ref) == kIOReturnSuccess) { + uint64_t timestamp = IOHIDValueGetTimeStamp(value_ref); + if (timestamp != _scroll_wheel_timestamp) { + int value = IOHIDValueGetIntegerValue(value_ref); + if (value != 0) { + // Just fire off a rapid down/up event. + double time = ClockObject::get_global_clock()->get_frame_time(); + ButtonHandle handle = (value > 0) ? MouseButton::wheel_up() : MouseButton::wheel_down(); + _button_events->add_event(ButtonEvent(handle, ButtonEvent::T_down, time)); + _button_events->add_event(ButtonEvent(handle, ButtonEvent::T_up, time)); + } + _scroll_wheel_timestamp = timestamp; + } + } + } +} + +#endif // __APPLE__ diff --git a/panda/src/device/ioKitInputDevice.h b/panda/src/device/ioKitInputDevice.h new file mode 100644 index 0000000000..1dfde3d034 --- /dev/null +++ b/panda/src/device/ioKitInputDevice.h @@ -0,0 +1,55 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ioKitInputDevice.h + * @author rdb + * @date 2017-12-21 + */ + +#ifndef IOKITINPUTDEVICE_H +#define IOKITINPUTDEVICE_H + +#include "pandabase.h" +#include "inputDevice.h" + +#if defined(__APPLE__) && !defined(CPPPARSER) + +#include + +/** + * This implementation uses the IOKit HID code that was introduced with macOS + * 10.5 to interface with USB HID devices. + */ +class EXPCL_PANDA_DEVICE IOKitInputDevice FINAL : public InputDevice { +public: + IOKitInputDevice(IOHIDDeviceRef device); + ~IOKitInputDevice(); + + void on_remove(); + +private: + void parse_element(IOHIDElementRef element); + + virtual void do_poll(); + + IOHIDDeviceRef _device; + pvector _button_elements; + pvector _analog_elements; + IOHIDElementRef _hat_element; + int _hat_x_axis; + IOHIDElementRef _pointer_x; + IOHIDElementRef _pointer_y; + IOHIDElementRef _scroll_wheel; + uint64_t _pointer_x_timestamp; + uint64_t _pointer_y_timestamp; + uint64_t _scroll_wheel_timestamp; +}; + +#endif // __APPLE__ + +#endif diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 90c0b54133..b9895f7880 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -8,6 +8,7 @@ #include "evdevInputDevice.cxx" #include "inputDevice.cxx" #include "inputDeviceManager.cxx" -#include "inputDeviceSet.cxx" -#include "linuxJoystickDevice.cxx" #include "inputDeviceNode.cxx" +#include "inputDeviceSet.cxx" +#include "ioKitInputDevice.cxx" +#include "linuxJoystickDevice.cxx" From ba8cbfd0564c6fa55f03cc2250cf449bf5067491 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 22 Dec 2017 17:09:20 +0100 Subject: [PATCH 188/424] input: fixes for Linux scoring and axis/button mapping This adds a list of known devices so that we can assign custom behavior to them. I tested this with the T.Flight Hotas X flight stick and the NVIDIA Shield Controller. --- panda/src/device/evdevInputDevice.cxx | 359 +++++++++++++++----------- panda/src/device/evdevInputDevice.h | 3 + panda/src/device/inputDevice.cxx | 8 +- panda/src/device/inputDevice.h | 4 +- 4 files changed, 223 insertions(+), 151 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 21ce8d2fa1..b6c8c193b9 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -25,52 +25,33 @@ #define test_bit(bit, array) ((array)[(bit)>>3] & (1<<((bit)&7))) -static InputDevice::ControlAxis axis_map[] = { - // ABS_X = 0x00 - InputDevice::C_left_x, - // ABS_Y = 0x01 - InputDevice::C_left_y, - // ABS_Z = 0x02 - InputDevice::C_left_trigger, - // ABS_RX = 0x03 - InputDevice::C_right_x, - // ABS_RY = 0x04 - InputDevice::C_right_y, - // ABS_RZ = 0x05 - InputDevice::C_right_trigger, - // ABS_THROTTLE = 0x06 - InputDevice::C_throttle, - // ABS_RUDDER = 0x07 - InputDevice::C_rudder, - // ABS_WHEEL = 0x08 - InputDevice::C_wheel, - // ABS_GAS = 0x09 - InputDevice::C_accelerator, - // ABS_BRAKE = 0x0a - InputDevice::C_brake, +enum QuirkBits { + // Right stick uses Z and Rz inputs. + QB_rstick_from_z = 1, - InputDevice::C_none, - InputDevice::C_none, - InputDevice::C_none, - InputDevice::C_none, - InputDevice::C_none, + // Throttle goes from -1 to 1 rather than from 0 to 1. + QB_centered_throttle = 2, - // ABS_HAT0X = 0x10 - InputDevice::C_hat_x, - // ABS_HAT0Y = 0x11 - InputDevice::C_hat_y, + // Throttle is reversed. + QB_reversed_throttle = 4, - // ABS_HAT1X = 0x12 - // ABS_HAT1Y = 0x13 - // ABS_HAT2X = 0x14 - // ABS_HAT2Y = 0x15 - // ABS_HAT3X = 0x16 - // ABS_HAT3Y = 0x17 - // ABS_PRESSURE = 0x18 - // ABS_DISTANCE = 0x19 - // ABS_TILT_X = 0x1a - // ABS_TILT_Y = 0x1b - // ABS_TOOL_WIDTH = 0x1c + // Only consider the device "connected" if all axes are non-zero. + QB_connect_if_nonzero = 8, +}; + +static const struct DeviceMapping { + unsigned short vendor; + unsigned short product; + InputDevice::DeviceClass device_class; + int quirks; +} mapping_presets[] = { + // NVIDIA Shield Controller + {0x0955, 0x7214, InputDevice::DC_gamepad, QB_rstick_from_z}, + // T.Flight Hotas X + {0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle}, + // Xbox 360 Wireless Controller + {0x045e, 0x0719, InputDevice::DC_gamepad, QB_connect_if_nonzero}, + {0}, }; TypeHandle EvdevInputDevice::_type_handle; @@ -214,6 +195,8 @@ bool EvdevInputDevice:: init_device() { nassertr(_fd >= 0, false); + LightMutexHolder holder(_lock); + uint8_t evtypes[(EV_CNT + 7) >> 3]; memset(evtypes, 0, sizeof(evtypes)); char name[128]; @@ -262,76 +245,89 @@ init_device() { has_axes = true; } + // Do we have a preset device mapping? + int quirks = 0; + const DeviceMapping *mapping = mapping_presets; + while (mapping->vendor != 0) { + if (_vendor_id == mapping->vendor && _product_id == mapping->product) { + _device_class = mapping->device_class; + quirks = mapping->quirks; + break; + } + ++mapping; + } // Try to detect which type of device we have here - int device_scores[DC_COUNT]; - memset(device_scores, 0, sizeof(device_scores)); + if (_device_class == DC_unknown) { + int device_scores[DC_COUNT]; + memset(device_scores, 0, sizeof(device_scores)); - // Test for specific keys - if (test_bit(BTN_GAMEPAD, keys)) { - device_scores[DC_gamepad] += 5; - device_scores[DC_steering_wheel] += 5; - device_scores[DC_flight_stick] += 5; - } + // Test for specific keys + if (test_bit(BTN_GAMEPAD, keys) && test_bit(ABS_X, axes) && test_bit(ABS_RX, axes)) { + device_scores[DC_gamepad] += 5; + device_scores[DC_steering_wheel] += 5; + device_scores[DC_flight_stick] += 5; + } - if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) { - device_scores[DC_steering_wheel] += 10; - } - if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) { - device_scores[DC_steering_wheel] += 10; - } - if (test_bit(BTN_JOYSTICK, keys)) { - device_scores[DC_flight_stick] += 10; - } - if (test_bit(BTN_MOUSE, keys)) { - device_scores[DC_mouse] += 20; - } - uint8_t unknown_keys[] = {KEY_POWER}; - for (int i = 0; i < 1; i++) { - if (test_bit(unknown_keys[i], keys)) { - if (unknown_keys[i] == KEY_POWER) { + if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) { + device_scores[DC_steering_wheel] += 10; + } + if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) { + device_scores[DC_steering_wheel] += 10; + } + if (test_bit(BTN_JOYSTICK, keys) && test_bit(ABS_X, axes)) { + device_scores[DC_flight_stick] += 10; + } + if (test_bit(BTN_MOUSE, keys) && test_bit(EV_REL, evtypes)) { + device_scores[DC_mouse] += 20; + } + uint8_t unknown_keys[] = {KEY_POWER}; + for (int i = 0; i < 1; i++) { + if (test_bit(unknown_keys[i], keys)) { + if (unknown_keys[i] == KEY_POWER) { + } + device_scores[DC_unknown] += 20; } - device_scores[DC_unknown] += 20; } - } - if (_flags & IDF_has_keyboard) { - device_scores[DC_keyboard] += 20; - } + if (_flags & IDF_has_keyboard) { + device_scores[DC_keyboard] += 20; + } - // Test for specific name tags - string lowercase_name = _name; - for(int x=0; x<_name.length(); x++) { - lowercase_name[x]=tolower(lowercase_name[x]); - } - if (lowercase_name.find("gamepad") != string::npos) { - device_scores[DC_gamepad] += 10; - } - if (lowercase_name.find("wheel") != string::npos) { - device_scores[DC_steering_wheel] += 10; - } - if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) { - device_scores[DC_mouse] += 10; - } - if (lowercase_name.find("keyboard") != string::npos) { - device_scores[DC_keyboard] += 10; - } - // List of lowercase names that occur in unknown devices - string unknown_names[] = {"video bus", "power button", "sleep button"}; - for(int i = 0; i < 3; i++) { - if (lowercase_name.find(unknown_names[i]) != string::npos) { - device_scores[DC_unknown] += 20; + // Test for specific name tags + string lowercase_name = _name; + for(int x=0; x<_name.length(); x++) { + lowercase_name[x]=tolower(lowercase_name[x]); + } + if (lowercase_name.find("gamepad") != string::npos) { + device_scores[DC_gamepad] += 10; + } + if (lowercase_name.find("wheel") != string::npos) { + device_scores[DC_steering_wheel] += 10; + } + if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) { + device_scores[DC_mouse] += 10; + } + if (lowercase_name.find("keyboard") != string::npos) { + device_scores[DC_keyboard] += 10; + } + // List of lowercase names that occur in unknown devices + string unknown_names[] = {"video bus", "power button", "sleep button"}; + for(int i = 0; i < 3; i++) { + if (lowercase_name.find(unknown_names[i]) != string::npos) { + device_scores[DC_unknown] += 20; + } } - } - // Check which device type got the most points - size_t highest_score = 0; - for (size_t i = 0; i < DC_COUNT; i++) { - if (device_scores[i] > highest_score) { - highest_score = device_scores[i]; - _device_class = (DeviceClass)i; + // Check which device type got the most points + size_t highest_score = 0; + for (size_t i = 0; i < DC_COUNT; i++) { + if (device_scores[i] > highest_score) { + highest_score = device_scores[i]; + _device_class = (DeviceClass)i; + } } + //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; } - //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; if (_device_class != DC_gamepad) { emulate_dpad = false; @@ -343,70 +339,140 @@ init_device() { memset(states, 0, sizeof(states)); ioctl(_fd, EVIOCGKEY(sizeof(states)), states); - int bi = 0; for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { - ButtonHandle mapped = map_button(i); - set_button_map(bi, mapped); + ButtonState button; + button.handle = map_button(i); - if (mapped == ButtonHandle::none()) { + int button_index = (int)_buttons.size(); + if (button.handle == ButtonHandle::none()) { if (device_cat.is_debug()) { device_cat.debug() << "Unmapped /dev/input/event" << _index - << " button " << bi << ": 0x" << hex << i << dec << "\n"; + << " button " << button_index << ": 0x" << hex << i << dec << "\n"; } } if (test_bit(i, states)) { - _buttons[bi].state = S_down; + button.state = S_down; all_values_zero = false; } else { - _buttons[bi].state = S_up; + button.state = S_up; } - if (_buttons[bi].handle == GamepadButton::dpad_left()) { + if (button.handle == GamepadButton::dpad_left()) { emulate_dpad = false; } - ++bi; + _buttons.push_back(button); + if (i >= _button_indices.size()) { + _button_indices.resize(i + 1, -1); + } + _button_indices[i] = button_index; } } } if (has_axes) { + _control_indices.resize(num_bits, -1); + for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { ControlAxis axis = C_none; - if (i >= ABS_HAT0X) { - // Emulate D-Pad buttons if necessary. - if (i == ABS_HAT0X && emulate_dpad) { + switch (i) { + case ABS_X: + axis = InputDevice::C_left_x; + break; + case ABS_Y: + axis = InputDevice::C_left_y; + break; + case ABS_Z: + if (quirks & QB_rstick_from_z) { + axis = InputDevice::C_right_x; + } else if (_device_class == DC_gamepad) { + axis = InputDevice::C_left_trigger; + } else { + axis = InputDevice::C_throttle; + } + break; + case ABS_RX: + if ((quirks & QB_rstick_from_z) == 0) { + axis = InputDevice::C_right_x; + } + break; + case ABS_RY: + if ((quirks & QB_rstick_from_z) == 0) { + axis = InputDevice::C_right_y; + } + break; + case ABS_RZ: + if (quirks & QB_rstick_from_z) { + axis = InputDevice::C_right_y; + } else if (_device_class == DC_gamepad) { + axis = InputDevice::C_right_trigger; + } else { + axis = InputDevice::C_twist; + } + break; + case ABS_THROTTLE: + axis = InputDevice::C_rudder; + break; + case ABS_RUDDER: + axis = InputDevice::C_rudder; + break; + case ABS_WHEEL: + axis = InputDevice::C_wheel; + break; + case ABS_GAS: + if (_device_class == DC_gamepad) { + axis = InputDevice::C_right_trigger; + } else { + axis = InputDevice::C_accelerator; + } + break; + case ABS_BRAKE: + if (_device_class == DC_gamepad) { + axis = InputDevice::C_left_trigger; + } else { + axis = InputDevice::C_brake; + } + break; + case ABS_HAT0X: + if (emulate_dpad) { _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_right())); - - } else if (i == ABS_HAT0Y && emulate_dpad) { + } else { + axis = C_hat_x; + } + break; + case ABS_HAT0Y: + if (emulate_dpad) { _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); - } else if (i == ABS_HAT0X) { - axis = C_hat_x; - } else if (i == ABS_HAT0Y) { + } else { axis = C_hat_y; } - } else { - axis = axis_map[i]; - //cerr << "Axis " << axis_map[i] << " is mapped by the driver to " << i << "\n"; + break; } // Check the initial value and ranges. struct input_absinfo absinfo; if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { - // Flip Y axis to match Windows implementation. - if (i == ABS_Y || i == ABS_RY) { - swap(absinfo.minimum, absinfo.maximum); + int index; + // We'd like to reverse the Y axis to match the XInput behavior. + // Also T.Flight Hotas X throttle is reversed and can go backwards. + if (axis == C_y || axis == C_left_y || axis == C_right_y || + (axis == C_throttle && (quirks & QB_reversed_throttle) != 0)) { + swap(absinfo.maximum, absinfo.minimum); } - - add_control(axis, absinfo.minimum, absinfo.maximum); - control_changed(axis, absinfo.value); + if (axis == C_throttle && (quirks & QB_centered_throttle) != 0) { + index = add_control(axis, absinfo.maximum, absinfo.minimum, true); + } else { + index = add_control(axis, absinfo.minimum, absinfo.maximum); + } + control_changed(index, absinfo.value); + _control_indices[i] = index; if (absinfo.value != 0) { all_values_zero = false; @@ -472,7 +538,7 @@ init_device() { // Special-case fix for Xbox 360 Wireless Receiver: the Linux kernel // driver always reports 4 connected gamepads, regardless of the number // of gamepads actually present. This hack partially remedies this. - if (all_values_zero && _vendor_id == 0x045e && _product_id == 0x0719) { + if (all_values_zero && (quirks & QB_connect_if_nonzero) != 0) { _is_connected = false; } else { _is_connected = true; @@ -520,6 +586,7 @@ process_events() { bool have_pointer = false; double time = ClockObject::get_global_clock()->get_frame_time(); ButtonHandle button; + int index; // It seems that some devices send a single EV_SYN event when being // unplugged. Boo. Ignore it. @@ -548,26 +615,18 @@ process_events() { button_changed(_dpad_up_button, events[i].value < 0); button_changed(_dpad_up_button+1, events[i].value > 0); } - control_changed(code, events[i].value); + nassertd(code >= 0 && code < _control_indices.size()) break; + index = _control_indices[code]; + if (index >= 0) { + control_changed(index, events[i].value); + } break; case EV_KEY: - button = map_button(code); - _button_events->add_event(ButtonEvent(button, events[i].value ? ButtonEvent::T_down : ButtonEvent::T_up, time)); - // set the buttons state - State state; - if (events[i].value) { - state = S_down; - } else { - state = S_up; - } - for (int i = 0; i < _buttons.size(); ++i) { - if (get_button(i).handle == button) { - //NOTE: set_button_state doesn't work correct here. - //set_button_state(i, state); - _buttons[i].state = state; - break; - } + nassertd(code >= 0 && code < _button_indices.size()) break; + index = _button_indices[code]; + if (index >= 0) { + button_changed(index, events[i].value != 0); } break; @@ -723,6 +782,14 @@ map_button(int code) { }; return keyboard_map[code]; + } else if (code == KEY_BACK) { + // Used by NVIDIA Shield Controller + return GamepadButton::back(); + + } else if (code == KEY_SEARCH) { + // Used by NVIDIA Shield Controller + return GamepadButton::guide(); + } else if (code < 0x100) { return ButtonHandle::none(); diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 849d72ae27..bd249b4d0e 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -44,6 +44,9 @@ private: int _ff_strong; int _ff_weak; + pvector _control_indices; + pvector _button_indices; + // These are used for D-pad emulation. int _dpad_x_axis; int _dpad_y_axis; diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index a5b1f1cd45..d1a5576c58 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -118,7 +118,7 @@ get_pointer_events() { /** * Called by the implementation to add a new known control. */ -void InputDevice:: +int InputDevice:: add_control(ControlAxis axis, int minimum, int maximum, bool centered) { AnalogState state; state.axis = axis; @@ -131,14 +131,16 @@ add_control(ControlAxis axis, int minimum, int maximum, bool centered) { state._scale = 1.0 / maximum; state._bias = 0.0; } + int index = (int)_controls.size(); _controls.push_back(state); + return index; } /** * Called by the implementation to add a new known control. This version * tries to guess whether the control is centered or not. */ -void InputDevice:: +int InputDevice:: add_control(ControlAxis axis, int minimum, int maximum) { bool centered = (minimum < 0) || axis == C_left_x @@ -150,7 +152,7 @@ add_control(ControlAxis axis, int minimum, int maximum) { || axis == C_wheel || axis == C_twist || axis == C_rudder; - add_control(axis, minimum, maximum, centered); + return add_control(axis, minimum, maximum, centered); } /** diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 7f0e459bc3..3dd5d698af 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -185,8 +185,8 @@ PUBLISHED: protected: // Called during the constructor to add new controls or buttons - void add_control(ControlAxis axis, int minimum, int maximum, bool centered); - void add_control(ControlAxis axis, int minimum, int maximum); + int add_control(ControlAxis axis, int minimum, int maximum, bool centered); + int add_control(ControlAxis axis, int minimum, int maximum); void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); From 6025bf0a8eb59598866096be97312ae6a351db38 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 22 Dec 2017 21:22:30 +0100 Subject: [PATCH 189/424] deploy-ng: fix error on Linux --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 46fc3a20e0..94375c4a8a 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -593,7 +593,7 @@ class build_apps(distutils.core.Command): elif magic == b'\x7FELF': # Elf magic. Used on (among others) Linux and FreeBSD. - deps = self._read_dependencies_elf(fp, os.path.dirname(source_path), search_path) + deps = self._read_dependencies_elf(fp, target_dir, search_path) elif magic in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'): # A Mach-O file, as used on macOS. From 58623b09bdaa687b8ab541d451cd35d52dca2e7b Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 3 Jan 2018 14:48:18 +0100 Subject: [PATCH 190/424] input: add missing has_vibration() method to InputDevice --- panda/src/device/inputDevice.I | 10 ++++++++++ panda/src/device/inputDevice.h | 1 + 2 files changed, 11 insertions(+) diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index c10ba4a172..f8be63a9c3 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -118,6 +118,16 @@ has_tracker() const { return ((_flags & IDF_has_tracker) != 0); } +/** + * Returns true if the device has vibration motors that can be controlled by + * calling set_vibration(). + */ +INLINE bool InputDevice:: +has_vibration() const { + LightMutexHolder holder(_lock); + return ((_flags & IDF_has_vibration) != 0); +} + /** * Returns true if the device may be able to provide information about its * battery life. diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 3dd5d698af..f4727e0a9c 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -149,6 +149,7 @@ PUBLISHED: INLINE bool has_pointer() const; INLINE bool has_keyboard() const; INLINE bool has_tracker() const; + INLINE bool has_vibration() const; INLINE bool has_battery() const; INLINE PointerData get_pointer() const; From 4f739e88cf6fa97a7af78b6f935003936e04242d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 14:50:54 +0100 Subject: [PATCH 191/424] input: joystick buttons, use yaw/pitch/roll for axes, more devices Also adds format_* functions to get string from enum value. --- panda/src/device/evdevInputDevice.cxx | 105 +++++++++--- panda/src/device/evdevInputDevice.h | 2 +- panda/src/device/inputDevice.cxx | 196 +++++++++++------------ panda/src/device/inputDevice.h | 25 ++- panda/src/device/linuxJoystickDevice.cxx | 32 ++-- panda/src/device/linuxJoystickDevice.h | 2 +- panda/src/device/xInputDevice.cxx | 6 +- panda/src/putil/gamepadButton.cxx | 30 ++++ panda/src/putil/gamepadButton.h | 6 + 9 files changed, 256 insertions(+), 148 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index b6c8c193b9..67fa1193c7 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -37,6 +37,9 @@ enum QuirkBits { // Only consider the device "connected" if all axes are non-zero. QB_connect_if_nonzero = 8, + + // ABS_THROTTLE maps to rudder + QB_rudder_from_throttle = 16, }; static const struct DeviceMapping { @@ -48,9 +51,25 @@ static const struct DeviceMapping { // NVIDIA Shield Controller {0x0955, 0x7214, InputDevice::DC_gamepad, QB_rstick_from_z}, // T.Flight Hotas X - {0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle}, + {0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle | QB_rudder_from_throttle}, // Xbox 360 Wireless Controller {0x045e, 0x0719, InputDevice::DC_gamepad, QB_connect_if_nonzero}, + // Jess Tech Colour Rumble Pad + {0x0f30, 0x0111, InputDevice::DC_gamepad, 0}, + // 3Dconnexion Space Traveller 3D Mouse + {0x046d, 0xc623, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Pilot 3D Mouse + {0x046d, 0xc625, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Navigator 3D Mouse + {0x046d, 0xc626, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Explorer 3D Mouse + {0x046d, 0xc627, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Navigator for Notebooks + {0x046d, 0xc628, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion SpacePilot Pro 3D Mouse + {0x046d, 0xc629, InputDevice::DC_3d_mouse, 0}, + // 3Dconnexion Space Mouse Pro + {0x046d, 0xc62b, InputDevice::DC_3d_mouse, 0}, {0}, }; @@ -329,10 +348,6 @@ init_device() { //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; } - if (_device_class != DC_gamepad) { - emulate_dpad = false; - } - if (has_keys) { // Also check whether the buttons are currently pressed. uint8_t states[(KEY_CNT + 7) >> 3]; @@ -342,7 +357,7 @@ init_device() { for (int i = 0; i < KEY_CNT; ++i) { if (test_bit(i, keys)) { ButtonState button; - button.handle = map_button(i); + button.handle = map_button(i, _device_class); int button_index = (int)_buttons.size(); if (button.handle == ButtonHandle::none()) { @@ -378,10 +393,22 @@ init_device() { ControlAxis axis = C_none; switch (i) { case ABS_X: - axis = InputDevice::C_left_x; + if (_device_class == DC_gamepad) { + axis = InputDevice::C_left_x; + } else if (_device_class == DC_flight_stick) { + axis = InputDevice::C_roll; + } else { + axis = InputDevice::C_x; + } break; case ABS_Y: - axis = InputDevice::C_left_y; + if (_device_class == DC_gamepad) { + axis = InputDevice::C_left_y; + } else if (_device_class == DC_flight_stick) { + axis = InputDevice::C_pitch; + } else { + axis = InputDevice::C_y; + } break; case ABS_Z: if (quirks & QB_rstick_from_z) { @@ -408,11 +435,15 @@ init_device() { } else if (_device_class == DC_gamepad) { axis = InputDevice::C_right_trigger; } else { - axis = InputDevice::C_twist; + axis = InputDevice::C_yaw; } break; case ABS_THROTTLE: - axis = InputDevice::C_rudder; + if (quirks & QB_rudder_from_throttle) { + axis = InputDevice::C_rudder; + } else { + axis = InputDevice::C_throttle; + } break; case ABS_RUDDER: axis = InputDevice::C_rudder; @@ -438,20 +469,26 @@ init_device() { if (emulate_dpad) { _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); - } else { - axis = C_hat_x; + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_left())); + _buttons.push_back(ButtonState(GamepadButton::hat_right())); + } } break; case ABS_HAT0Y: if (emulate_dpad) { _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); - } else { - axis = C_hat_y; + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_up())); + _buttons.push_back(ButtonState(GamepadButton::hat_down())); + } } break; } @@ -461,8 +498,9 @@ init_device() { if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { int index; // We'd like to reverse the Y axis to match the XInput behavior. + // Also reverse the yaw axis to match right-hand coordinate system. // Also T.Flight Hotas X throttle is reversed and can go backwards. - if (axis == C_y || axis == C_left_y || axis == C_right_y || + if (axis == C_yaw || axis == C_left_y || axis == C_right_y || (axis == C_throttle && (quirks & QB_reversed_throttle) != 0)) { swap(absinfo.maximum, absinfo.minimum); } @@ -647,7 +685,7 @@ process_events() { * Static function to map an evdev code to a ButtonHandle. */ ButtonHandle EvdevInputDevice:: -map_button(int code) { +map_button(int code, DeviceClass device_class) { if (code >= 0 && code < 0x80) { // See linux/input.h for the source of this mapping. static const ButtonHandle keyboard_map[] = { @@ -802,12 +840,33 @@ map_button(int code) { } else { return MouseButton::button(code - BTN_MOUSE); } + + } else if ((code & 0xfff0) == BTN_JOYSTICK) { + if (device_class == DC_gamepad) { + // Based on "Jess Tech Colour Rumble Pad" + static const ButtonHandle mapping[] = { + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::lshoulder(), + GamepadButton::ltrigger(), + GamepadButton::rshoulder(), + GamepadButton::rtrigger(), + GamepadButton::back(), + GamepadButton::start(), + GamepadButton::lstick(), + GamepadButton::rstick(), + }; + if ((code & 0xf) < 12) { + return mapping[code & 0xf]; + } + } else { + return GamepadButton::joystick(code & 0xf); + } } switch (code) { - case BTN_TRIGGER: - return GamepadButton::trigger(); - case BTN_A: return GamepadButton::action_a(); diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index bd249b4d0e..9ebaef03f2 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -54,7 +54,7 @@ private: int _dpad_up_button; public: - static ButtonHandle map_button(int code); + static ButtonHandle map_button(int code, DeviceClass device_class = DC_unknown); public: static TypeHandle get_class_type() { diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index d1a5576c58..76aa79dc58 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -143,14 +143,17 @@ add_control(ControlAxis axis, int minimum, int maximum, bool centered) { int InputDevice:: add_control(ControlAxis axis, int minimum, int maximum) { bool centered = (minimum < 0) + || axis == C_x + || axis == C_y + || axis == C_z + || axis == C_yaw + || axis == C_pitch + || axis == C_roll || axis == C_left_x || axis == C_left_y || axis == C_right_x || axis == C_right_y - || axis == C_x - || axis == C_y || axis == C_wheel - || axis == C_twist || axis == C_rudder; return add_control(axis, minimum, maximum, centered); } @@ -499,130 +502,125 @@ void InputDevice:: do_poll() { } -ostream & -operator << (ostream &out, InputDevice::DeviceClass dc) { +/** + * Returns a string describing the given device class enumerant. + */ +string InputDevice:: +format_device_class(DeviceClass dc) { switch (dc) { case InputDevice::DC_unknown: - out << "unknown"; - break; + return "unknown"; case InputDevice::DC_virtual: - out << "virtual"; - break; + return "virtual"; case InputDevice::DC_keyboard: - out << "keyboard"; - break; + return "keyboard"; case InputDevice::DC_mouse: - out << "mouse"; - break; + return "mouse"; case InputDevice::DC_touch: - out << "touch"; - break; + return "touch"; case InputDevice::DC_gamepad: - out << "gamepad"; - break; + return "gamepad"; case InputDevice::DC_flight_stick: - out << "flight_stick"; - break; + return "flight_stick"; case InputDevice::DC_steering_wheel: - out << "steering_wheel"; - break; + return "steering_wheel"; case InputDevice::DC_dance_pad: - out << "dance_pad"; - break; + return "dance_pad"; case InputDevice::DC_hmd: - out << "hmd"; - break; + return "hmd"; + + case InputDevice::DC_3d_mouse: + return "3d_mouse"; case InputDevice::DC_COUNT: break; } + return "**invalid**"; +} + +/** + * Returns a string describing the given axis enumerant. + */ +string InputDevice:: +format_axis(ControlAxis axis) { + switch (axis) { + case InputDevice::C_none: + return "none"; + + case InputDevice::C_x: + return "x"; + + case InputDevice::C_y: + return "y"; + + case InputDevice::C_z: + return "z"; + + case InputDevice::C_yaw: + return "yaw"; + + case InputDevice::C_pitch: + return "pitch"; + + case InputDevice::C_roll: + return "roll"; + + case InputDevice::C_left_x: + return "left_x"; + + case InputDevice::C_left_y: + return "left_y"; + + case InputDevice::C_left_trigger: + return "left_trigger"; + + case InputDevice::C_right_x: + return "right_x"; + + case InputDevice::C_right_y: + return "right_y"; + + case InputDevice::C_right_trigger: + return "right_trigger"; + + //case InputDevice::C_trigger: + // return "trigger"; + + case InputDevice::C_throttle: + return "throttle"; + + case InputDevice::C_rudder: + return "rudder"; + + case InputDevice::C_wheel: + return "wheel"; + + case InputDevice::C_accelerator: + return "accelerator"; + + case InputDevice::C_brake: + return "brake"; + } + return "**invalid**"; +} + +ostream & +operator << (ostream &out, InputDevice::DeviceClass dc) { + out << InputDevice::format_device_class(dc); return out; } ostream & operator << (ostream &out, InputDevice::ControlAxis axis) { - switch (axis) { - case InputDevice::C_none: - out << "none"; - break; - - case InputDevice::C_left_x: - out << "left_x"; - break; - - case InputDevice::C_left_y: - out << "left_y"; - break; - - case InputDevice::C_left_trigger: - out << "left_trigger"; - break; - - case InputDevice::C_right_x: - out << "right_x"; - break; - - case InputDevice::C_right_y: - out << "right_y"; - break; - - case InputDevice::C_right_trigger: - out << "right_trigger"; - break; - - case InputDevice::C_x: - out << "x"; - break; - - case InputDevice::C_y: - out << "y"; - break; - - case InputDevice::C_trigger: - out << "trigger"; - break; - - case InputDevice::C_throttle: - out << "throttle"; - break; - - case InputDevice::C_twist: - out << "twist"; - break; - - case InputDevice::C_rudder: - out << "rudder"; - break; - - case InputDevice::C_hat_x: - out << "hat_x"; - break; - - case InputDevice::C_hat_y: - out << "hat_y"; - break; - - case InputDevice::C_wheel: - out << "wheel"; - break; - - case InputDevice::C_accelerator: - out << "accelerator"; - break; - - case InputDevice::C_brake: - out << "brake"; - break; - } - + out << InputDevice::format_axis(axis); return out; } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index f4727e0a9c..43dbe14efe 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -78,6 +78,9 @@ PUBLISHED: // Head-mounted display. DC_hmd, + // 3D mouse, such as produced by 3Dconnexion. + DC_3d_mouse, + // Count of this enum, used for loops DC_COUNT, }; @@ -95,6 +98,16 @@ PUBLISHED: enum ControlAxis { C_none, + // Generic translational axes + C_x, + C_y, + C_z, + + // Generic rotational axes, used by joysticks and 3D mice + C_yaw, + C_pitch, + C_roll, + // Gamepad C_left_x, C_left_y, @@ -103,15 +116,9 @@ PUBLISHED: C_right_y, C_right_trigger, - // Flight stick - C_x, - C_y, - C_trigger, + // Flight stick specific C_throttle, - C_twist, - C_rudder, - C_hat_x, - C_hat_y, + C_rudder, // When available separately from yaw // Steering wheel / pedals C_wheel, @@ -183,6 +190,8 @@ PUBLISHED: PT(PointerEventList) get_pointer_events(); virtual void output(ostream &out) const; + static string format_device_class(DeviceClass dc); + static string format_axis(ControlAxis axis); protected: // Called during the constructor to add new controls or buttons diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 8cd612f2d7..9aca81ad3d 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -251,7 +251,7 @@ open_device() { if (_device_class == DC_gamepad) { axis = C_left_trigger; } else { - axis = C_trigger; + //axis = C_trigger; } break; @@ -288,28 +288,34 @@ open_device() { break; case ABS_HAT0X: - if (_dpad_left_button == -1 && _device_class == DC_gamepad) { - // Emulate D-Pad. + if (_dpad_left_button == -1) { + // Emulate D-Pad or hat switch. _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_left())); + _buttons.push_back(ButtonState(GamepadButton::hat_right())); + } axis = C_none; - } else { - axis = C_hat_x; } break; case ABS_HAT0Y: - if (_dpad_up_button == -1 && _device_class == DC_gamepad) { + if (_dpad_up_button == -1) { // Emulate D-Pad. _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_up())); + _buttons.push_back(ButtonState(GamepadButton::hat_down())); + } axis = C_none; - } else { - axis = C_hat_y; } break; @@ -323,7 +329,7 @@ open_device() { } _controls[i].axis = axis; - if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { + if (axis == C_left_trigger || axis == C_right_trigger) { // We'd like to use 0.0 to indicate the resting position. _controls[i]._scale = 1.0 / 65534.0; _controls[i]._bias = 0.5; diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index 6507d4d80a..7bd8203039 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -39,7 +39,7 @@ private: int _fd; int _index; - // These are used for D-pad emulation. + // These are used for D-pad / hat switch emulation. int _dpad_x_axis; int _dpad_y_axis; int _dpad_left_button; diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 04d88996f5..57fa73a416 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -271,10 +271,10 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { case XINPUT_DEVSUBTYPE_FLIGHT_STICK: _device_class = DC_flight_stick; - set_control_map(0, C_rudder); + set_control_map(0, C_yaw); set_control_map(1, C_throttle); - set_control_map(2, C_x); - set_control_map(3, C_y); + set_control_map(2, C_roll); + set_control_map(3, C_pitch); set_control_map(4, C_hat_x); set_control_map(5, C_hat_y); break; diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index 7ddbc33b17..fc580e6b5e 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -48,6 +48,32 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(action_1) DEFINE_GAMEPAD_BUTTON_HANDLE(action_2) DEFINE_GAMEPAD_BUTTON_HANDLE(trigger) +DEFINE_GAMEPAD_BUTTON_HANDLE(hat_up) +DEFINE_GAMEPAD_BUTTON_HANDLE(hat_down) +DEFINE_GAMEPAD_BUTTON_HANDLE(hat_left) +DEFINE_GAMEPAD_BUTTON_HANDLE(hat_right) + +/** + * Returns the ButtonHandle associated with the particular numbered joystick + * button (zero-based), if there is one, or ButtonHandle::none() if there is + * not. + */ +ButtonHandle GamepadButton:: +joystick(int button_number) { + if (button_number >= 0) { + // "button1" does not exist, it is called "trigger" instead + static pvector buttons(1, _trigger); + while (button_number >= buttons.size()) { + char numstr[20]; + sprintf(numstr, "joystick%d", (int)buttons.size() + 1); + ButtonHandle handle; + ButtonRegistry::ptr()->register_button(handle, numstr); + buttons.push_back(handle); + } + return buttons[button_number]; + } + return ButtonHandle::none(); +} /** * This is intended to be called only once, by the static initialization @@ -85,4 +111,8 @@ init_gamepad_buttons() { ButtonRegistry::ptr()->register_button(_action_2, "action_2"); ButtonRegistry::ptr()->register_button(_trigger, "trigger"); + ButtonRegistry::ptr()->register_button(_hat_up, "hat_up"); + ButtonRegistry::ptr()->register_button(_hat_down, "hat_down"); + ButtonRegistry::ptr()->register_button(_hat_left, "hat_left"); + ButtonRegistry::ptr()->register_button(_hat_right, "hat_right"); } diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index 0edbbad99b..768f634380 100644 --- a/panda/src/putil/gamepadButton.h +++ b/panda/src/putil/gamepadButton.h @@ -53,7 +53,13 @@ PUBLISHED: static ButtonHandle action_1(); static ButtonHandle action_2(); + // Flight stick buttons, takes zero-based index. First is always trigger. static ButtonHandle trigger(); + static ButtonHandle joystick(int button_number); + static ButtonHandle hat_up(); + static ButtonHandle hat_down(); + static ButtonHandle hat_left(); + static ButtonHandle hat_right(); public: static void init_gamepad_buttons(); From 9fbdefe12073ef653c5605d3989808ab7eae4203 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 17:26:48 +0100 Subject: [PATCH 192/424] input: add analog trigger emulation on Linux --- panda/src/device/evdevInputDevice.cxx | 29 +++++- panda/src/device/evdevInputDevice.h | 5 + panda/src/device/linuxJoystickDevice.cxx | 125 ++++++----------------- panda/src/device/linuxJoystickDevice.h | 5 + 4 files changed, 68 insertions(+), 96 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 67fa1193c7..c94c71df98 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -90,7 +90,9 @@ EvdevInputDevice(int index) : _dpad_x_axis(-1), _dpad_y_axis(-1), _dpad_left_button(-1), - _dpad_up_button(-1) { + _dpad_up_button(-1), + _ltrigger_code(-1), + _rtrigger_code(-1) { char path[64]; sprintf(path, "/dev/input/event%d", index); @@ -239,6 +241,7 @@ init_device() { bool all_values_zero = true; bool emulate_dpad = true; + bool have_analog_triggers = false; bool has_keys = false; bool has_axes = false; @@ -375,7 +378,12 @@ init_device() { } if (button.handle == GamepadButton::dpad_left()) { emulate_dpad = false; + } else if (button.handle == GamepadButton::ltrigger()) { + _ltrigger_code = i; + } else if (button.handle == GamepadButton::rtrigger()) { + _rtrigger_code = i; } + _buttons.push_back(button); if (i >= _button_indices.size()) { _button_indices.resize(i + 1, -1); @@ -415,6 +423,7 @@ init_device() { axis = InputDevice::C_right_x; } else if (_device_class == DC_gamepad) { axis = InputDevice::C_left_trigger; + have_analog_triggers = true; } else { axis = InputDevice::C_throttle; } @@ -434,6 +443,7 @@ init_device() { axis = InputDevice::C_right_y; } else if (_device_class == DC_gamepad) { axis = InputDevice::C_right_trigger; + have_analog_triggers = true; } else { axis = InputDevice::C_yaw; } @@ -454,6 +464,7 @@ init_device() { case ABS_GAS: if (_device_class == DC_gamepad) { axis = InputDevice::C_right_trigger; + have_analog_triggers = true; } else { axis = InputDevice::C_accelerator; } @@ -461,6 +472,7 @@ init_device() { case ABS_BRAKE: if (_device_class == DC_gamepad) { axis = InputDevice::C_left_trigger; + have_analog_triggers = true; } else { axis = InputDevice::C_brake; } @@ -541,6 +553,16 @@ init_device() { } } + if (_ltrigger_code >= 0 && _rtrigger_code >= 0 && !have_analog_triggers) { + // Emulate analog triggers. + _ltrigger_control = (int)_controls.size(); + add_control(C_left_trigger, 0, 1, false); + add_control(C_right_trigger, 0, 1, false); + } else { + _ltrigger_code = -1; + _rtrigger_code = -1; + } + char path[64]; char buffer[256]; sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); @@ -666,6 +688,11 @@ process_events() { if (index >= 0) { button_changed(index, events[i].value != 0); } + if (code == _ltrigger_code) { + control_changed(_ltrigger_control, events[i].value); + } else if (code == _rtrigger_code) { + control_changed(_ltrigger_control + 1, events[i].value); + } break; default: diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 9ebaef03f2..0a6baa058a 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -53,6 +53,11 @@ private: int _dpad_left_button; int _dpad_up_button; + // This is used for axis emulation. + int _ltrigger_control; + int _ltrigger_code; + int _rtrigger_code; + public: static ButtonHandle map_button(int code, DeviceClass device_class = DC_unknown); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 9aca81ad3d..7c62ce2ae5 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -12,6 +12,7 @@ */ #include "linuxJoystickDevice.h" +#include "evdevInputDevice.h" #ifdef PHAVE_LINUX_INPUT_H @@ -32,7 +33,9 @@ LinuxJoystickDevice(int index) : _dpad_x_axis(-1), _dpad_y_axis(-1), _dpad_left_button(-1), - _dpad_up_button(-1) + _dpad_up_button(-1), + _ltrigger_button(-1), + _rtrigger_button(-1) { LightMutexHolder holder(_lock); if (!open_device()) { @@ -107,6 +110,8 @@ open_device() { ioctl(_fd, JSIOCGNAME(sizeof(name)), name); _name = name; + bool have_analog_triggers = false; + // Get the number of axes. uint8_t num_axes = 0, num_buttons = 0; ioctl(_fd, JSIOCGAXES, &num_axes); @@ -120,104 +125,18 @@ open_device() { ioctl(_fd, JSIOCGBTNMAP, btnmap); for (uint8_t i = 0; i < num_buttons; ++i) { - ButtonHandle handle = ButtonHandle::none(); - switch (btnmap[i]) { - case BTN_A: - handle = GamepadButton::action_a(); - _device_class = DC_gamepad; - break; - - case BTN_B: - handle = GamepadButton::action_b(); - break; - - case BTN_C: - handle = GamepadButton::action_c(); - break; - - case BTN_X: - handle = GamepadButton::action_x(); - break; - - case BTN_Y: - handle = GamepadButton::action_y(); - break; - - case BTN_Z: - handle = GamepadButton::action_z(); - break; - - case BTN_TL: - handle = GamepadButton::lshoulder(); - break; - - case BTN_TR: - handle = GamepadButton::rshoulder(); - break; - - case BTN_TL2: - handle = GamepadButton::ltrigger(); - break; - - case BTN_TR2: - handle = GamepadButton::rtrigger(); - break; - - case BTN_1: - handle = GamepadButton::action_1(); - break; - - case BTN_2: - handle = GamepadButton::action_2(); - break; - - case BTN_SELECT: - case KEY_PREVIOUS: - handle = GamepadButton::back(); - break; - - case BTN_START: - case KEY_NEXT: - handle = GamepadButton::start(); - break; - - case BTN_MODE: - handle = GamepadButton::guide(); - break; - - case BTN_THUMBL: - handle = GamepadButton::lstick(); - break; - - case BTN_THUMBR: - handle = GamepadButton::rstick(); - break; - - case BTN_TRIGGER_HAPPY1: - handle = GamepadButton::dpad_left(); - _dpad_left_button = i; - break; - - case BTN_TRIGGER_HAPPY2: - handle = GamepadButton::dpad_right(); - break; - - case BTN_TRIGGER_HAPPY3: - handle = GamepadButton::dpad_up(); - _dpad_up_button = i; - break; - - case BTN_TRIGGER_HAPPY4: - handle = GamepadButton::dpad_down(); - break; - - default: + ButtonHandle handle = EvdevInputDevice::map_button(btnmap[i]); + if (handle == ButtonHandle::none()) { if (device_cat.is_debug()) { device_cat.debug() << "Unmapped /dev/input/js" << _index << " button " << (int)i << ": 0x" << hex << btnmap[i] << "\n"; } - handle = ButtonHandle::none(); - break; + } else if (handle == GamepadButton::action_a()) { + _device_class = DC_gamepad; + } else if (handle == GamepadButton::ltrigger()) { + _ltrigger_button = i; + } else if (handle == GamepadButton::rtrigger()) { + _rtrigger_button = i; } _buttons[i].handle = handle; } @@ -333,6 +252,7 @@ open_device() { // We'd like to use 0.0 to indicate the resting position. _controls[i]._scale = 1.0 / 65534.0; _controls[i]._bias = 0.5; + have_analog_triggers = true; } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { _controls[i]._scale = 1.0 / -32767.0; _controls[i]._bias = 0.0; @@ -343,6 +263,16 @@ open_device() { } } + if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) { + // Emulate analog triggers. + _ltrigger_control = (int)_controls.size(); + add_control(C_left_trigger, 0, 1, false); + add_control(C_right_trigger, 0, 1, false); + } else { + _ltrigger_button = -1; + _rtrigger_button = -1; + } + // Get additional information from sysfs. sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index); FILE *f = fopen(path, "r"); @@ -454,6 +384,11 @@ process_events() { int index = events[i].number; if (events[i].type & JS_EVENT_BUTTON) { + if (index == _ltrigger_button) { + control_changed(_ltrigger_control, events[i].value); + } else if (index == _rtrigger_button) { + control_changed(_ltrigger_control + 1, events[i].value); + } button_changed(index, (events[i].value != 0)); } else if (events[i].type & JS_EVENT_AXIS) { diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index 7bd8203039..ab97dd5293 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -45,6 +45,11 @@ private: int _dpad_left_button; int _dpad_up_button; + // This is used for axis emulation. + int _ltrigger_control; + int _ltrigger_button; + int _rtrigger_button; + public: static TypeHandle get_class_type() { return _type_handle; From 2523bb032679202d645a285bb8eef5243151c913 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 17:36:52 +0100 Subject: [PATCH 193/424] input: fixes for 3D mouse axes on Linux --- panda/src/device/evdevInputDevice.cxx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index c94c71df98..cf20e57261 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -424,17 +424,23 @@ init_device() { } else if (_device_class == DC_gamepad) { axis = InputDevice::C_left_trigger; have_analog_triggers = true; + } else if (_device_class == DC_3d_mouse) { + axis = InputDevice::C_z; } else { axis = InputDevice::C_throttle; } break; case ABS_RX: - if ((quirks & QB_rstick_from_z) == 0) { + if (_device_class == DC_3d_mouse) { + axis = InputDevice::C_pitch; + } else if ((quirks & QB_rstick_from_z) == 0) { axis = InputDevice::C_right_x; } break; case ABS_RY: - if ((quirks & QB_rstick_from_z) == 0) { + if (_device_class == DC_3d_mouse) { + axis = InputDevice::C_roll; + } else if ((quirks & QB_rstick_from_z) == 0) { axis = InputDevice::C_right_y; } break; @@ -513,7 +519,8 @@ init_device() { // Also reverse the yaw axis to match right-hand coordinate system. // Also T.Flight Hotas X throttle is reversed and can go backwards. if (axis == C_yaw || axis == C_left_y || axis == C_right_y || - (axis == C_throttle && (quirks & QB_reversed_throttle) != 0)) { + (axis == C_throttle && (quirks & QB_reversed_throttle) != 0) || + (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z))) { swap(absinfo.maximum, absinfo.minimum); } if (axis == C_throttle && (quirks & QB_centered_throttle) != 0) { From 96f6bd7fa566532a57a2fb3cc2a0e590c212422c Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 17:39:44 +0100 Subject: [PATCH 194/424] input: fixes for joysticks in Linux compatibility mode --- panda/src/device/linuxJoystickDevice.cxx | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 7c62ce2ae5..f510e02f6a 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -133,6 +133,8 @@ open_device() { } } else if (handle == GamepadButton::action_a()) { _device_class = DC_gamepad; + } else if (handle == GamepadButton::trigger()) { + _device_class = DC_flight_stick; } else if (handle == GamepadButton::ltrigger()) { _ltrigger_button = i; } else if (handle == GamepadButton::rtrigger()) { @@ -152,17 +154,21 @@ open_device() { switch (axmap[i]) { case ABS_X: if (_device_class == DC_gamepad) { - axis = C_left_x; + axis = InputDevice::C_left_x; + } else if (_device_class == DC_flight_stick) { + axis = InputDevice::C_roll; } else { - axis = C_x; + axis = InputDevice::C_x; } break; case ABS_Y: if (_device_class == DC_gamepad) { - axis = C_left_y; + axis = InputDevice::C_left_y; + } else if (_device_class == DC_flight_stick) { + axis = InputDevice::C_pitch; } else { - axis = C_y; + axis = InputDevice::C_y; } break; @@ -183,7 +189,11 @@ open_device() { break; case ABS_RZ: - axis = C_right_trigger; + if (_device_class == DC_gamepad) { + axis = InputDevice::C_right_trigger; + } else { + axis = InputDevice::C_yaw; + } break; case ABS_THROTTLE: From 73b4217cad150548779396cf1e1a497d2b78f255 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Jan 2018 19:34:49 +0100 Subject: [PATCH 195/424] input: fix joystick button and axis mapping on macOS --- panda/src/device/ioKitInputDevice.cxx | 65 +++++++++------------------ panda/src/device/ioKitInputDevice.h | 2 +- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index ec0522b1a0..3b18b3546c 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -100,9 +100,11 @@ IOKitInputDevice(IOHIDDeviceRef device) : CFRelease(elements); if (_hat_element != nullptr) { - _hat_x_axis = _controls.size(); - add_control(C_hat_x, -1, 1, true); - add_control(C_hat_y, -1, 1, true); + _hat_left_button = (int)_buttons.size(); + _buttons.push_back(ButtonState(GamepadButton::hat_left())); + _buttons.push_back(ButtonState(GamepadButton::hat_right())); + _buttons.push_back(ButtonState(GamepadButton::hat_down())); + _buttons.push_back(ButtonState(GamepadButton::hat_up())); } if (_pointer_x != nullptr && _pointer_y != nullptr) { @@ -163,6 +165,8 @@ parse_element(IOHIDElementRef element) { case kHIDUsage_GD_X: if (_device_class == DC_gamepad) { axis = C_left_x; + } else if (_device_class == DC_flight_stick) { + axis = C_roll; } else if (_device_class == DC_mouse) { _pointer_x = element; return; @@ -173,6 +177,8 @@ parse_element(IOHIDElementRef element) { case kHIDUsage_GD_Y: if (_device_class == DC_gamepad) { axis = C_left_y; + } else if (_device_class == DC_flight_stick) { + axis = C_pitch; } else if (_device_class == DC_mouse) { _pointer_y = element; return; @@ -197,7 +203,7 @@ parse_element(IOHIDElementRef element) { if (_device_class == DC_gamepad) { axis = C_right_trigger; } else { - axis = C_twist; + axis = C_yaw; } break; case kHIDUsage_GD_Slider: @@ -253,8 +259,9 @@ parse_element(IOHIDElementRef element) { if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. add_control(axis, max, min, true); - } else if (axis == C_y || axis == C_left_y || axis == C_right_y) { + } else if (axis == C_yaw || axis == C_left_y || axis == C_right_y) { // We'd like to reverse the Y axis to match the XInput behavior. + // We also reverse yaw to obey the right-hand rule. add_control(axis, max, min); } else { add_control(axis, min, max); @@ -614,6 +621,10 @@ parse_element(IOHIDElementRef element) { if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { handle = gamepad_buttons[usage]; } + } else if (_device_class == DC_flight_stick) { + if (usage > 0) { + handle = GamepadButton::joystick(usage - 1); + } } else if (_device_class == DC_mouse) { // In Panda, wheel and right button are flipped around... int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); @@ -681,46 +692,10 @@ do_poll() { IOHIDValueRef value_ref; if (IOHIDDeviceGetValue(_device, _hat_element, &value_ref) == kIOReturnSuccess) { int value = IOHIDValueGetIntegerValue(value_ref); - int x = 0; - int y = 0; - switch (value) { - case 0: - x = 0; - y = -1; - break; - case 1: - x = 1; - y = -1; - break; - case 2: - x = 1; - y = 0; - break; - case 3: - x = 1; - y = 1; - break; - case 4: - x = 0; - y = 1; - break; - case 5: - x = -1; - y = 1; - break; - case 6: - x = -1; - y = 0; - break; - case 7: - x = -1; - y = -1; - break; - default: - break; - } - control_changed(_hat_x_axis, x); - control_changed(_hat_x_axis + 1, y); + button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left + button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right + button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down + button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up } } diff --git a/panda/src/device/ioKitInputDevice.h b/panda/src/device/ioKitInputDevice.h index 1dfde3d034..5adc0205f7 100644 --- a/panda/src/device/ioKitInputDevice.h +++ b/panda/src/device/ioKitInputDevice.h @@ -41,7 +41,7 @@ private: pvector _button_elements; pvector _analog_elements; IOHIDElementRef _hat_element; - int _hat_x_axis; + int _hat_left_button; IOHIDElementRef _pointer_x; IOHIDElementRef _pointer_y; IOHIDElementRef _scroll_wheel; From 8bd617c4eebe1dc517d7433190a2a9e47c34be23 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 22 Jan 2018 21:58:26 +0100 Subject: [PATCH 196/424] input: new windows input manager based on raw input The new implementation uses a message-only window to register raw input events. XInput is still used when possible, but raw input is being used to handle device detection. (XInput is only enabled when an XInput device is plugged in, which also prevents us from having to load and poll the XInput library unnecessarily.) For raw devices, the Windows HID parser library is used to parse the raw data. Unfortunately, the Windows 7.1 SDK does not ship hid.lib (which is in the WDK 7.1.0), so I am dynamically loading hid.dll to prevent pulling in a dependency on the WDK. (But perhaps we can ship hid.lib in the thirdparty libraries?) Input devices other than XInput gamepads and the 3D mouse have not been tested very well yet. Adding keyboard and mouse support is still a TODO. This also splits out the Windows implementation of InputDeviceManager into a separate subclass; it is intended that the other implementations will follow suit. --- dtool/src/parser-inc/XInput.h | 8 +- panda/src/device/inputDevice.cxx | 7 +- panda/src/device/inputDeviceManager.I | 8 +- panda/src/device/inputDeviceManager.cxx | 68 +-- panda/src/device/inputDeviceManager.h | 19 +- panda/src/device/inputDeviceNode.cxx | 4 +- panda/src/device/p3device_composite1.cxx | 2 + panda/src/device/winInputDeviceManager.cxx | 384 ++++++++++++ panda/src/device/winInputDeviceManager.h | 64 ++ panda/src/device/winRawInputDevice.cxx | 655 +++++++++++++++++++++ panda/src/device/winRawInputDevice.h | 85 +++ panda/src/device/xInputDevice.cxx | 208 ++++--- panda/src/device/xInputDevice.h | 12 +- 13 files changed, 1350 insertions(+), 174 deletions(-) create mode 100644 panda/src/device/winInputDeviceManager.cxx create mode 100644 panda/src/device/winInputDeviceManager.h create mode 100644 panda/src/device/winRawInputDevice.cxx create mode 100644 panda/src/device/winRawInputDevice.h diff --git a/dtool/src/parser-inc/XInput.h b/dtool/src/parser-inc/XInput.h index 21a2802287..0674961fc0 100644 --- a/dtool/src/parser-inc/XInput.h +++ b/dtool/src/parser-inc/XInput.h @@ -1,3 +1,5 @@ -struct XINPUT_STATE; -struct XINPUT_CAPABILITIES; -struct XINPUT_VIBRATION; +typedef struct _XINPUT_STATE XINPUT_STATE, *PXINPUT_STATE; +typedef struct _XINPUT_CAPABILITIES XINPUT_CAPABILITIES, *PXINPUT_CAPABILITIES; +typedef struct _XINPUT_VIBRATION XINPUT_VIBRATION, *PXINPUT_VIBRATION; +typedef struct _XINPUT_GAMEPAD XINPUT_GAMEPAD, *PXINPUT_GAMEPAD; +typedef struct _XINPUT_KEYSTROKE XINPUT_KEYSTROKE, *PXINPUT_KEYSTROKE; diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 76aa79dc58..1aa2a03ca4 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -13,6 +13,10 @@ #include "inputDevice.h" +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define fma(a, b, c) ((a) * (b) + (c)) +#endif + TypeHandle InputDevice::_type_handle; /** @@ -29,7 +33,8 @@ InputDevice(const string &name, DeviceClass dev_class, int flags) : _event_sequence(0), _enable_pointer_events(false), _battery_level(-1), - _max_battery_level(-1) + _max_battery_level(-1), + _lock("InputDevice") { _button_events = new ButtonEventList; } diff --git a/panda/src/device/inputDeviceManager.I b/panda/src/device/inputDeviceManager.I index b38dc6d2d5..731051aa3c 100644 --- a/panda/src/device/inputDeviceManager.I +++ b/panda/src/device/inputDeviceManager.I @@ -16,8 +16,10 @@ */ INLINE InputDeviceManager *InputDeviceManager:: get_global_ptr() { - if (_global_ptr == (InputDeviceManager *)NULL) { - _global_ptr = new InputDeviceManager; + if (_global_ptr != nullptr) { + return _global_ptr; + } else { + make_global_ptr(); + return _global_ptr; } - return _global_ptr; } diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index d7f6a04955..31f7f93934 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -14,6 +14,7 @@ #include "inputDeviceManager.h" #include "ioKitInputDevice.h" #include "linuxJoystickDevice.h" +#include "winInputDeviceManager.h" #include "throw_event.h" #ifdef PHAVE_LINUX_INPUT_H @@ -23,12 +24,6 @@ #include #endif -static ConfigVariableDouble xinput_detection_delay -("xinput-detection-delay", 0.5, - PRC_DESC("How many seconds to wait between each check to see whether " - "an XInput has been connected. This check is not done every " - "frame in order to prevent slowdown.")); - InputDeviceManager *InputDeviceManager::_global_ptr = NULL; /** @@ -80,35 +75,6 @@ InputDeviceManager() : _inotify_fd(-1) { return; } } -#elif defined(_WIN32) -InputDeviceManager:: -InputDeviceManager() : - _xinput_device0(0), - _xinput_device1(1), - _xinput_device2(2), - _xinput_device3(3), - _last_detection(0.0) { - - // XInput provides four device slots, so we simply create four XInputDevice - // objects that are bound to the lifetime of the input manager. - _xinput_device0.local_object(); - _xinput_device1.local_object(); - _xinput_device2.local_object(); - _xinput_device3.local_object(); - - if (_xinput_device0.is_connected()) { - _connected_devices.add_device(&_xinput_device0); - } - if (_xinput_device1.is_connected()) { - _connected_devices.add_device(&_xinput_device1); - } - if (_xinput_device2.is_connected()) { - _connected_devices.add_device(&_xinput_device2); - } - if (_xinput_device3.is_connected()) { - _connected_devices.add_device(&_xinput_device3); - } -} #elif defined(__APPLE__) InputDeviceManager:: InputDeviceManager() { @@ -153,7 +119,7 @@ InputDeviceManager() { } #else InputDeviceManager:: -InputDeviceManager() { +InputDeviceManager() : _lock("InputDeviceManager") { } #endif @@ -174,6 +140,18 @@ InputDeviceManager:: #endif } +/** + * Creates the global input manager. + */ +void InputDeviceManager:: +make_global_ptr() { +#ifdef _WIN32 + _global_ptr = new WinInputDeviceManager; +#else + _global_ptr = new InputDeviceManager; +#endif +} + #ifdef PHAVE_LINUX_INPUT_H /** * Checks whether the event device with the given index is accessible, and if @@ -455,24 +433,6 @@ update() { ptr += sizeof(inotify_event) + event->len; } #endif - -#ifdef _WIN32 - // XInput doesn't provide a very good hot-plugging interface. We just check - // whether it's connected every so often. Perhaps we can switch to using - // RegisterDeviceNotification in the future. - double time_now = ClockObject::get_global_clock()->get_real_time(); - LightMutexHolder holder(_update_lock); - - if (time_now - _last_detection > xinput_detection_delay.get_value()) { - // I've heard this can be quite slow if no device is detected. We - // should probably move it to a thread. - _xinput_device0.detect(this); - _xinput_device1.detect(this); - _xinput_device2.detect(this); - _xinput_device3.detect(this); - _last_detection = time_now; - } -#endif } #if defined(__APPLE__) && !defined(CPPPARSER) diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index a262b60d28..c6c50a33d9 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -21,6 +21,7 @@ #ifdef _WIN32 #include "xinputDevice.h" +class WinRawInputDevice; #endif #ifdef __APPLE__ @@ -32,10 +33,12 @@ * when a device has been hot-plugged. */ class EXPCL_PANDA_DEVICE InputDeviceManager { -private: +protected: InputDeviceManager(); ~InputDeviceManager(); + static void make_global_ptr(); + #ifdef PHAVE_LINUX_INPUT_H InputDevice *consider_add_evdev_device(int index); InputDevice *consider_add_js_device(int index); @@ -48,14 +51,14 @@ PUBLISHED: void add_device(InputDevice *device); void remove_device(InputDevice *device); - void update(); + virtual void update(); INLINE static InputDeviceManager *get_global_ptr(); // The set of all currently connected devices. MAKE_PROPERTY(devices, get_devices); -private: +protected: LightMutex _lock; #ifdef PHAVE_LINUX_INPUT_H @@ -65,16 +68,6 @@ private: InputDeviceSet _inactive_devices; #endif -#ifdef _WIN32 - // There are always exactly four of these in existence. - LightMutex _update_lock; - XInputDevice _xinput_device0; - XInputDevice _xinput_device1; - XInputDevice _xinput_device2; - XInputDevice _xinput_device3; - double _last_detection; -#endif - #if defined(__APPLE__) && !defined(CPPPARSER) IOHIDManagerRef _hid_manager; diff --git a/panda/src/device/inputDeviceNode.cxx b/panda/src/device/inputDeviceNode.cxx index 662b2dbac8..e68fd8652b 100644 --- a/panda/src/device/inputDeviceNode.cxx +++ b/panda/src/device/inputDeviceNode.cxx @@ -61,11 +61,11 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, } // calculate the battery level in percent and set a warning if the level is to low - if (_device->has_battery()) { + /*if (_device->has_battery()) { short bl = _device->get_battery_level(); short bl_percent = bl / (_device->get_max_battery_level() / (short)100); if (bl_percent <= low_battery_level) { output.set_data(_low_battery_event_output, EventParameter(1)); } - } + }*/ } diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index b9895f7880..65a03ccc48 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -12,3 +12,5 @@ #include "inputDeviceSet.cxx" #include "ioKitInputDevice.cxx" #include "linuxJoystickDevice.cxx" +#include "winInputDeviceManager.cxx" +#include "winRawInputDevice.cxx" diff --git a/panda/src/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx new file mode 100644 index 0000000000..124229b5ea --- /dev/null +++ b/panda/src/device/winInputDeviceManager.cxx @@ -0,0 +1,384 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winInputDeviceManager.cxx + * @author rdb + * @date 2018-01-21 + */ + +#include "winInputDeviceManager.h" +#include "winRawInputDevice.h" +#include "throw_event.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +/** + * Initializes the input device manager by scanning which devices are currently + * connected and setting up any platform-dependent structures necessary for + * listening for future device connect events. + */ +WinInputDeviceManager:: +WinInputDeviceManager() : + _xinput_device0(0), + _xinput_device1(1), + _xinput_device2(2), + _xinput_device3(3), + _message_hwnd(nullptr) { + + // XInput provides four device slots, so we simply create four XInputDevice + // objects that are bound to the lifetime of the input manager. + _xinput_device0.local_object(); + _xinput_device1.local_object(); + _xinput_device2.local_object(); + _xinput_device3.local_object(); + +// This function is only available in Vista and later, so we use a wrapper. + HMODULE module = LoadLibraryA("cfgmgr32.dll"); + if (module) { + _CM_Get_DevNode_PropertyW = (pCM_Get_DevNode_Property)GetProcAddress(module, "CM_Get_DevNode_PropertyW"); + } else { + _CM_Get_DevNode_PropertyW = nullptr; + } + + // Now create a message-only window for the raw input. + WNDCLASSEX wc = {}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.lpfnWndProc = window_proc; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpszClassName = "InputDeviceManager"; + if (!RegisterClassEx(&wc)) { + device_cat.warning() + << "Failed to register message-only window class.\n"; + return; + } + + _message_hwnd = CreateWindowEx(0, wc.lpszClassName, "InputDeviceManager", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr); + if (!_message_hwnd) { + device_cat.warning() + << "Failed to create message-only window.\n"; + return; + } + + // Now listen for raw input devices using the created message loop. + RAWINPUTDEVICE rid[3]; + rid[0].usUsagePage = 1; + rid[0].usUsage = 4; // Joysticks + rid[0].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; + rid[0].hwndTarget = _message_hwnd; + rid[1].usUsagePage = 1; + rid[1].usUsage = 5; // Gamepads + rid[1].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; + rid[1].hwndTarget = _message_hwnd; + rid[2].usUsagePage = 1; + rid[2].usUsage = 8; // Multi-axis controllers (including 3D mice) + rid[2].dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; + rid[2].hwndTarget = _message_hwnd; + if (!RegisterRawInputDevices(rid, 3, sizeof(RAWINPUTDEVICE))) { + device_cat.warning() + << "Failed to register raw input devices.\n"; + } +} + +/** + * Closes any resources that the device manager was using to listen for events. + */ +WinInputDeviceManager:: +~WinInputDeviceManager() { + if (_message_hwnd != nullptr) { + DestroyWindow(_message_hwnd); + _message_hwnd = nullptr; + } +} + +/** + * Called by the raw input device destructor. + */ +void WinInputDeviceManager:: +device_destroyed(WinRawInputDevice *device) { + LightMutexHolder holder(_lock); + // It shouldn't be in here, but let's check to be sure. + if (device->_handle != nullptr) { + _raw_devices.erase(device->_handle); + } + + _raw_devices_by_path.erase(device->_path); +} + +/** + * Called upon receiving a WM_INPUT message. + */ +void WinInputDeviceManager:: +on_input(HRAWINPUT handle) { + UINT size; + if (GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)) < 0) { + return; + } + + PRAWINPUT data = (PRAWINPUT)alloca(size); + if (GetRawInputData(handle, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER)) <= 0) { + return; + } + + // Look up the device in the map. + PT(WinRawInputDevice) device; + { + LightMutexHolder holder(_lock); + auto it = _raw_devices.find(data->header.hDevice); + if (it != _raw_devices.end()) { + device = it->second; + } + } + if (device != nullptr) { + device->on_input(data); + } +} + +/** + * Called upon receiving WM_INPUT_DEVICE_CHANGE with wparam GIDC_ARRIVAL. + */ +void WinInputDeviceManager:: +on_input_device_arrival(HANDLE handle) { + // Get the device path. + UINT size; + if (GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, nullptr, &size) != 0) { + return; + } + + char *path = (char *)alloca(size); + if (path == nullptr || + GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, (void *)path, &size) < 0) { + return; + } + + if (device_cat.is_debug()) { + device_cat.debug() + << "GIDC_ARRIVAL: " << path << "\n"; + } + + // Get the device info. + RID_DEVICE_INFO info; + info.cbSize = sizeof(RID_DEVICE_INFO); + size = sizeof(RID_DEVICE_INFO); + if (GetRawInputDeviceInfoA(handle, RIDI_DEVICEINFO, &info, &size) <= 0) { + return; + } + + // Strip the \\?\ prefix from the path. + while (path[0] == '\\' || path[0] == '?' || path[0] == '.') { + ++path; + } + + // Now, replace # with \\ in the path, but only up to three components. + char *p = path; + int i = 0; + while (*p != '\0') { + if (*p == '#') { + if (i++ < 2) { + *p = '\\'; + } else { + *p = '\0'; + break; + } + } + ++p; + } + + // Find the device node, which will be something like "HID\VID_0123..." + // Then we walk the device tree upward to get the USB node, which which will + // be something like a "USB\VID..." node, from which we can fetch the real + // USB device information (such as the product name). + string name, manufacturer; + DEVINST inst; + CONFIGRET ret = CM_Locate_DevNodeA(&inst, (DEVINSTID_A)path, CM_LOCATE_DEVNODE_PHANTOM); + if (ret == CR_SUCCESS) { + char buffer[4096]; + ULONG buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) { + name.assign(buffer); + } + buflen = 4096; + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) { + if (strcmp(buffer, "(Standard system devices)") != 0) { + manufacturer.assign(buffer); + } + } + + // Now walk the device tree upwards so fetch the bus-reported name of the + // parent USB device, which we prefer over the regular device description + // that is probably boring like "HID-compliant game controller". + DEVINST cur = inst; + DEVINST parent; + while (CM_Get_Parent(&parent, cur, 0) == CR_SUCCESS) { + buflen = 4096; + string dev_class; + if (CM_Get_DevNode_Registry_Property(parent, CM_DRP_CLASS, 0, buffer, &buflen, 0) == CR_SUCCESS) { + if (strcmp(buffer, "USB") == 0) { + // This is some generic USB device, like a hub. We've gone too far. + break; + } + dev_class.assign(buffer); + } + cur = parent; + + // While we're at it, maybe this one defines a manufacturer? + buflen = 4096; + if (manufacturer.empty() && + CM_Get_DevNode_Registry_Property(cur, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) { + if (strcmp(buffer, "(Standard system devices)") != 0) { + manufacturer.assign(buffer); + } + } + + // If it's a generic HID device, take the name from the USB bus. + // See devpkey.h for the available property keys. + static const DEVPROPKEY bus_reported_device_desc = { + {0x540b947e, 0x8b40, 0x45bc, {0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2}}, + 4, + }; + DEVPROPTYPE type; + buflen = 4096; + if (dev_class == "HIDClass" && _CM_Get_DevNode_PropertyW != nullptr && + _CM_Get_DevNode_PropertyW(cur, &bus_reported_device_desc, &type, (PBYTE)buffer, &buflen, 0) == CR_SUCCESS && + type == DEVPROP_TYPE_STRING) { + TextEncoder encoder; + name.assign(encoder.encode_wtext((wchar_t *)buffer)); + break; + } else { + buflen = 4096; + if (CM_Get_DevNode_Registry_Property(cur, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) { + // We'll pass if it has this awfully boring name. Is there a + // language-independent way to check this? + if (strcmp(buffer, "USB Input Device") != 0) { + name.assign(buffer); + } + } + } + } + } else if (device_cat.is_debug()) { + // No big deal, we just won't be able to get the name. + device_cat.debug() + << "Could not locate device node " << path << " (" << ret << ")\n"; + } + + // Is this an XInput device? If so, handle it via XInput, which allows us + // to handle independent left/right triggers as well as vibration output. + if (info.dwType == RIM_TYPEHID && strstr(path, "&IG_") != nullptr) { + // This is a device we should handle via the XInput API. Check which of + // the four players was the lucky one. + WinRawInputDevice idev(this, path); + if (_xinput_device0.check_arrival(info, inst, name, manufacturer)) { + add_device(&_xinput_device0); + } + if (_xinput_device1.check_arrival(info, inst, name, manufacturer)) { + add_device(&_xinput_device1); + } + if (_xinput_device2.check_arrival(info, inst, name, manufacturer)) { + add_device(&_xinput_device2); + } + if (_xinput_device3.check_arrival(info, inst, name, manufacturer)) { + add_device(&_xinput_device3); + } + return; + } + + LightMutexHolder holder(_lock); + + // Do we have a device by this path already? This can happen if the + // user keeps around a pointer to a disconnected device in the hope that + // it will reconnect later. + PT(WinRawInputDevice) device; + auto it = _raw_devices_by_path.find(path); + if (it != _raw_devices_by_path.end()) { + device = it->second; + } else { + device = new WinRawInputDevice(this, path); + _raw_devices_by_path[path] = device; + } + + if (device->on_arrival(handle, info, move(name))) { + _raw_devices[handle] = device; + _connected_devices.add_device(device); + + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered input device " << *device << "\n"; + } + throw_event("connect-device", device.p()); + } +} + +/** + * Called upon receiving WM_INPUT_DEVICE_CHANGE with wparam GIDC_REMOVAL. + */ +void WinInputDeviceManager:: +on_input_device_removal(HANDLE handle) { + // The handle will probably no longer be valid after this, so find the + // device and remove it from _raw_devices. However, we keep it in + // _raw_devices_by_path in case there's still a pointer around to it. + + // We keep the pointer outside the lock because the input device + // destructor calls back to InputDeviceManager. + PT(WinRawInputDevice) device; + { + LightMutexHolder holder(_lock); + auto it = _raw_devices.find(handle); + if (it != _raw_devices.end()) { + device = move(it->second); + _raw_devices.erase(it); + device->on_removal(); + + if (_connected_devices.remove_device(device)) { + throw_event("disconnect-device", device.p()); + } + if (device_cat.is_debug()) { + device_cat.debug() + << "Removed input device " << *device << "\n"; + } + } + } +} + +/** + * Implementation of the message loop. + */ +LRESULT WINAPI WinInputDeviceManager:: +window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WinInputDeviceManager *mgr; + switch (msg) { + case WM_INPUT: + mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr(); + if (mgr != nullptr) { + mgr->on_input((HRAWINPUT)lparam); + } + break; + + case WM_INPUT_DEVICE_CHANGE: + switch (LOWORD(wparam)) { + case GIDC_ARRIVAL: + mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr(); + if (mgr != nullptr) { + mgr->on_input_device_arrival((HANDLE)lparam); + } + break; + + case GIDC_REMOVAL: + mgr = (WinInputDeviceManager *)InputDeviceManager::get_global_ptr(); + if (mgr != nullptr) { + mgr->on_input_device_removal((HANDLE)lparam); + } + break; + } + break; + + default: + break; + } + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +#endif diff --git a/panda/src/device/winInputDeviceManager.h b/panda/src/device/winInputDeviceManager.h new file mode 100644 index 0000000000..de2b0dc04e --- /dev/null +++ b/panda/src/device/winInputDeviceManager.h @@ -0,0 +1,64 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winInputDeviceManager.h + * @author rdb + * @date 2018-01-21 + */ + +#ifndef WININPUTDEVICEMANAGER_H +#define WININPUTDEVICEMANAGER_H + +#include "inputDeviceManager.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +#include "xinputDevice.h" + +#include +#include + +class WinRawInputDevice; + +/** + * This is the Windows implementation of InputDeviceManager, managing both + * XInput controllers and raw input devices. + */ +class EXPCL_PANDA_DEVICE WinInputDeviceManager FINAL : public InputDeviceManager { +private: + WinInputDeviceManager(); + ~WinInputDeviceManager(); + +public: + void device_destroyed(WinRawInputDevice *device); + + void on_input(HRAWINPUT handle); + void on_input_device_arrival(HANDLE handle); + void on_input_device_removal(HANDLE handle); + +private: + // There are always exactly four of these in existence. + XInputDevice _xinput_device0; + XInputDevice _xinput_device1; + XInputDevice _xinput_device2; + XInputDevice _xinput_device3; + + HWND _message_hwnd; + pmap _raw_devices; + pmap _raw_devices_by_path; + + static LRESULT WINAPI window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + + typedef CONFIGRET (*pCM_Get_DevNode_Property)(DEVINST, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE, PULONG, ULONG); + pCM_Get_DevNode_Property _CM_Get_DevNode_PropertyW; + + friend class InputDeviceManager; +}; + +#endif +#endif diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx new file mode 100644 index 0000000000..68e8e89bf3 --- /dev/null +++ b/panda/src/device/winRawInputDevice.cxx @@ -0,0 +1,655 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winRawInputDevice.cxx + * @author rdb + * @date 2018-01-19 + */ + +#include "winRawInputDevice.h" +#include "gamepadButton.h" +#include "mouseButton.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +#include +#include + +// Copy definitions from hidusage.h, until we can drop support for the 7.1 SDK +typedef USHORT USAGE, *PUSAGE; + +#define HID_USAGE_PAGE_UNDEFINED ((USAGE) 0x00) +#define HID_USAGE_PAGE_GENERIC ((USAGE) 0x01) +#define HID_USAGE_PAGE_SIMULATION ((USAGE) 0x02) +#define HID_USAGE_PAGE_VR ((USAGE) 0x03) +#define HID_USAGE_PAGE_SPORT ((USAGE) 0x04) +#define HID_USAGE_PAGE_GAME ((USAGE) 0x05) +#define HID_USAGE_PAGE_KEYBOARD ((USAGE) 0x07) +#define HID_USAGE_PAGE_LED ((USAGE) 0x08) +#define HID_USAGE_PAGE_BUTTON ((USAGE) 0x09) + +#define HID_USAGE_GENERIC_POINTER ((USAGE) 0x01) +#define HID_USAGE_GENERIC_MOUSE ((USAGE) 0x02) +#define HID_USAGE_GENERIC_JOYSTICK ((USAGE) 0x04) +#define HID_USAGE_GENERIC_GAMEPAD ((USAGE) 0x05) +#define HID_USAGE_GENERIC_KEYBOARD ((USAGE) 0x06) +#define HID_USAGE_GENERIC_KEYPAD ((USAGE) 0x07) +#define HID_USAGE_GENERIC_SYSTEM_CTL ((USAGE) 0x80) + +#define HID_USAGE_GENERIC_X ((USAGE) 0x30) +#define HID_USAGE_GENERIC_Y ((USAGE) 0x31) +#define HID_USAGE_GENERIC_Z ((USAGE) 0x32) +#define HID_USAGE_GENERIC_RX ((USAGE) 0x33) +#define HID_USAGE_GENERIC_RY ((USAGE) 0x34) +#define HID_USAGE_GENERIC_RZ ((USAGE) 0x35) +#define HID_USAGE_GENERIC_SLIDER ((USAGE) 0x36) +#define HID_USAGE_GENERIC_DIAL ((USAGE) 0x37) +#define HID_USAGE_GENERIC_WHEEL ((USAGE) 0x38) +#define HID_USAGE_GENERIC_HATSWITCH ((USAGE) 0x39) + +// Copy definitions from hidpi.h, until we can drop support for the 7.1 SDK +#define HIDP_STATUS_SUCCESS ((NTSTATUS)(0x11 << 16)) + +typedef enum _HIDP_REPORT_TYPE { + HidP_Input, + HidP_Output, + HidP_Feature +} HIDP_REPORT_TYPE; + +typedef struct _HIDP_BUTTON_CAPS { + USAGE UsagePage; + UCHAR ReportID; + BOOLEAN IsAlias; + USHORT BitField; + USHORT LinkCollection; + USAGE LinkUsage; + USAGE LinkUsagePage; + BOOLEAN IsRange; + BOOLEAN IsStringRange; + BOOLEAN IsDesignatorRange; + BOOLEAN IsAbsolute; + ULONG Reserved[10]; + union { + struct { + USAGE UsageMin, UsageMax; + USHORT StringMin, StringMax; + USHORT DesignatorMin, DesignatorMax; + USHORT DataIndexMin, DataIndexMax; + } Range; + struct { + USAGE Usage, Reserved1; + USHORT StringIndex, Reserved2; + USHORT DesignatorIndex, Reserved3; + USHORT DataIndex, Reserved4; + } NotRange; + }; +} HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS; + +typedef struct _HIDP_VALUE_CAPS { + USAGE UsagePage; + UCHAR ReportID; + BOOLEAN IsAlias; + USHORT BitField; + USHORT LinkCollection; + USAGE LinkUsage; + USAGE LinkUsagePage; + BOOLEAN IsRange; + BOOLEAN IsStringRange; + BOOLEAN IsDesignatorRange; + BOOLEAN IsAbsolute; + BOOLEAN HasNull; + UCHAR Reserved; + USHORT BitSize; + USHORT ReportCount; + USHORT Reserved2[5]; + ULONG UnitsExp; + ULONG Units; + LONG LogicalMin, LogicalMax; + LONG PhysicalMin, PhysicalMax; + union { + struct { + USAGE UsageMin, UsageMax; + USHORT StringMin, StringMax; + USHORT DesignatorMin, DesignatorMax; + USHORT DataIndexMin, DataIndexMax; + } Range; + struct { + USAGE Usage, Reserved1; + USHORT StringIndex, Reserved2; + USHORT DesignatorIndex, Reserved3; + USHORT DataIndex, Reserved4; + } NotRange; + }; +} HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS; + +typedef PUCHAR PHIDP_REPORT_DESCRIPTOR; +typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA; + +typedef struct _HIDP_CAPS { + USAGE Usage; + USAGE UsagePage; + USHORT InputReportByteLength; + USHORT OutputReportByteLength; + USHORT FeatureReportByteLength; + USHORT Reserved[17]; + USHORT NumberLinkCollectionNodes; + USHORT NumberInputButtonCaps; + USHORT NumberInputValueCaps; + USHORT NumberInputDataIndices; + USHORT NumberOutputButtonCaps; + USHORT NumberOutputValueCaps; + USHORT NumberOutputDataIndices; + USHORT NumberFeatureButtonCaps; + USHORT NumberFeatureValueCaps; + USHORT NumberFeatureDataIndices; +} HIDP_CAPS, *PHIDP_CAPS; + +typedef struct _HIDP_DATA { + USHORT DataIndex; + USHORT Reserved; + union { + ULONG RawValue; + BOOLEAN On; + }; +} HIDP_DATA, *PHIDP_DATA; + +typedef LONG NTSTATUS; +typedef NTSTATUS (*pHidP_GetCaps)(PHIDP_PREPARSED_DATA, PHIDP_CAPS); +typedef NTSTATUS (*pHidP_GetButtonCaps)(HIDP_REPORT_TYPE, PHIDP_BUTTON_CAPS, PUSHORT, PHIDP_PREPARSED_DATA); +typedef NTSTATUS (*pHidP_GetValueCaps)(HIDP_REPORT_TYPE, PHIDP_VALUE_CAPS, PUSHORT, PHIDP_PREPARSED_DATA); +typedef NTSTATUS (*pHidP_GetData)(HIDP_REPORT_TYPE, PHIDP_DATA, PULONG, PHIDP_PREPARSED_DATA, PCHAR, ULONG); +typedef ULONG (*pHidP_MaxDataListLength)(HIDP_REPORT_TYPE, PHIDP_PREPARSED_DATA); + +static pHidP_GetCaps _HidP_GetCaps = nullptr; +static pHidP_GetButtonCaps _HidP_GetButtonCaps = nullptr; +static pHidP_GetValueCaps _HidP_GetValueCaps = nullptr; +static pHidP_GetData _HidP_GetData = nullptr; +static pHidP_MaxDataListLength _HidP_MaxDataListLength = nullptr; + +/** + * Static method to initialize the HID parser library. We load it dynamically + * because the Windows 7.1 SDK doesn't ship hid.lib. + */ +static bool init_hidp() { + HMODULE module = LoadLibraryA("hid.dll"); + if (module) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Successfully loaded hid.dll\n"; + } + + _HidP_GetCaps = (pHidP_GetCaps)GetProcAddress(module, "HidP_GetCaps"); + _HidP_GetButtonCaps = (pHidP_GetButtonCaps)GetProcAddress(module, "HidP_GetButtonCaps"); + _HidP_GetValueCaps = (pHidP_GetValueCaps)GetProcAddress(module, "HidP_GetValueCaps"); + _HidP_GetData = (pHidP_GetData)GetProcAddress(module, "HidP_GetData"); + _HidP_MaxDataListLength = (pHidP_MaxDataListLength)GetProcAddress(module, "HidP_MaxDataListLength"); + + if (_HidP_GetCaps == nullptr || _HidP_GetButtonCaps == nullptr || + _HidP_GetValueCaps == nullptr || _HidP_GetData == nullptr || + _HidP_MaxDataListLength == nullptr) { + device_cat.error() + << "Failed to locate function pointers in hid.dll\n"; + return false; + } + + return true; + } + + device_cat.error() + << "Failed to load hid.dll.\n"; + return false; +} + +/** + * Protected constructor. Given a raw device handle. + */ +WinRawInputDevice:: +WinRawInputDevice(WinInputDeviceManager *manager, const char *path) : + _manager(manager), + _path(path), + _max_data_count(0), + _preparsed(nullptr) { +} + +/** + * + */ +WinRawInputDevice:: +~WinRawInputDevice() { + // Unregister the device from the manager. + LightMutexHolder holder(_lock); + if (_manager != nullptr) { + _manager->device_destroyed(this); + } + if (_preparsed != nullptr) { + free(_preparsed); + _preparsed = nullptr; + } +} + +/** + * Called by InputDeviceManager when this device is connected. Returns true + * if the device was connected successfully. + */ +bool WinRawInputDevice:: +on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name) { + LightMutexHolder holder(_lock); + + _name = move(name); + + switch (info.dwType) { + case RIM_TYPEMOUSE: + _device_class = DC_mouse; + break; + + case RIM_TYPEKEYBOARD: + _device_class = DC_keyboard; + break; + + case RIM_TYPEHID: + _vendor_id = info.hid.dwVendorId; + _product_id = info.hid.dwProductId; + + // Gamepads + if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_GAMEPAD) { + _device_class = DC_gamepad; + + // Flight sticks + } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) { + _device_class = DC_flight_stick; + + // Mice + } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) { + _device_class = DC_mouse; + + // Keyboards + } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && + info.hid.usUsage == HID_USAGE_GENERIC_KEYBOARD) { + _device_class = DC_keyboard; + + // 3Dconnexion SpaceNavigator and friends. + } else if (_vendor_id == 0x046d && + (_product_id == 0xc623 || + _product_id == 0xc625 || + _product_id == 0xc626 || + _product_id == 0xc627 || + _product_id == 0xc628 || + _product_id == 0xc629 || + _product_id == 0xc62b)) { + _device_class = DC_3d_mouse; + } + break; + + default: + return false; + } + + // Initialize hid.dll, which provides the HID parser functions. + static bool hid_initialized = false; + if (!hid_initialized) { + if (!init_hidp()) { + return false; + } + hid_initialized = true; + } + + // Get the "preparsed data", which we can parse with the HID parser API. + UINT size = 0; + if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) < 0) { + return false; + } + + PHIDP_PREPARSED_DATA buffer = (PHIDP_PREPARSED_DATA)malloc(size); + if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, buffer, &size) <= 0) { + return false; + } + _preparsed = buffer; + + HIDP_CAPS caps; + if (_HidP_GetCaps(buffer, &caps) != HIDP_STATUS_SUCCESS) { + device_cat.warning() + << "Failed to get capabilities from HID preparsed data.\n"; + return false; + } + + // Prepare a mapping of data indices to button/control indices. + _indices.resize(caps.NumberInputDataIndices); + + _buttons.clear(); + _controls.clear(); + + USHORT num_button_caps = caps.NumberInputButtonCaps; + PHIDP_BUTTON_CAPS button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS)); + _HidP_GetButtonCaps(HidP_Input, button_caps, &num_button_caps, buffer); + + for (USHORT i = 0; i < num_button_caps; ++i) { + HIDP_BUTTON_CAPS &cap = button_caps[i]; + int upper = 0; + if (cap.IsRange) { + upper = (cap.Range.UsageMax - cap.Range.UsageMin); + + if (device_cat.is_debug()) { + device_cat.debug() + << "Found button range: DataIndex=" << dec + << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax + << ", ReportID=" << (int)cap.ReportID + << ", UsagePage=0x" << hex << cap.UsagePage + << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax + << dec << "\n"; + } + } else { + if (device_cat.is_debug()) { + device_cat.debug() + << "Found button: DataIndex=" << dec << cap.NotRange.DataIndex + << ", ReportID=" << dec << (int)cap.ReportID + << ", UsagePage=0x" << cap.UsagePage + << ", Usage=0x" << cap.NotRange.Usage + << dec << "\n"; + } + } + + // Windows will only tell us which buttons in a report are "on", so we + // need to keep track of which buttons exist in which report so that we + // can figure out which ones are off. + if (cap.ReportID >= _report_buttons.size()) { + _report_buttons.resize(cap.ReportID + 1); + } + for (int j = 0; j <= upper; ++j) { + USAGE usage = j + cap.Range.UsageMin; + USHORT data_index = j + cap.Range.DataIndexMin; + ButtonHandle handle = ButtonHandle::none(); + switch (cap.UsagePage) { + case HID_USAGE_PAGE_BUTTON: + if (_device_class == DC_gamepad) { + static const ButtonHandle gamepad_buttons[] = { + ButtonHandle::none(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::start(), + GamepadButton::back(), + GamepadButton::lstick(), + GamepadButton::rstick(), + }; + if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + handle = gamepad_buttons[usage]; + } + } else if (_device_class == DC_flight_stick) { + if (usage > 0) { + handle = GamepadButton::joystick(usage - 1); + } + } else if (_device_class == DC_mouse) { + // In Panda, wheel and right button are flipped around... + int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); + handle = MouseButton::button(button); + } + break; + } + + int button_index = _buttons.size(); + _report_buttons[cap.ReportID].set_bit(button_index); + _indices[data_index] = Index::button(button_index); + _buttons.push_back(ButtonState(handle)); + } + } + + USHORT num_value_caps = caps.NumberInputValueCaps; + PHIDP_VALUE_CAPS value_caps = (PHIDP_VALUE_CAPS)alloca(num_value_caps * sizeof(HIDP_VALUE_CAPS)); + _HidP_GetValueCaps(HidP_Input, value_caps, &num_value_caps, buffer); + + _hat_data_index = -1; + + for (USHORT i = 0; i < num_value_caps; ++i) { + HIDP_VALUE_CAPS &cap = value_caps[i]; + int upper = 0; + if (cap.IsRange) { + upper = (cap.Range.UsageMax - cap.Range.UsageMin); + + if (device_cat.is_debug()) { + device_cat.debug() + << "Found value range: DataIndex=" << dec + << cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax + << ", ReportID=" << (int)cap.ReportID + << ", UsagePage=0x" << hex << cap.UsagePage + << ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax + << dec << ", LogicalMin=" << cap.LogicalMin + << ", LogicalMax=" << cap.LogicalMax << "\n"; + } + } else { + if (device_cat.is_debug()) { + device_cat.debug() + << "Found value: DataIndex=" << dec << cap.NotRange.DataIndex + << ", ReportID=" << dec << (int)cap.ReportID + << ", UsagePage=0x" << hex << cap.UsagePage + << ", Usage=0x" << cap.NotRange.Usage + << dec << ", LogicalMin=" << cap.LogicalMin + << ", LogicalMax=" << cap.LogicalMax << "\n"; + } + } + + for (int j = 0; j <= upper; ++j) { + USAGE usage = j + cap.Range.UsageMin; + USHORT data_index = j + cap.Range.DataIndexMin; + bool is_signed = true; + + // My gamepads give this odd invalid range. + if (cap.LogicalMin == 0 && cap.LogicalMax == -1) { + cap.LogicalMax = 65535; + is_signed = false; + } + + ControlAxis axis = C_none; + switch (cap.UsagePage) { + case HID_USAGE_PAGE_GENERIC: + switch (usage) { + case HID_USAGE_GENERIC_X: + if (_device_class == DC_gamepad) { + axis = C_left_x; + } else if (_device_class == DC_flight_stick) { + axis = C_roll; + } else { + axis = C_x; + } + break; + case HID_USAGE_GENERIC_Y: + if (_device_class == DC_gamepad) { + axis = C_left_y; + swap(cap.LogicalMin, cap.LogicalMax); + } else if (_device_class == DC_flight_stick) { + axis = C_pitch; + } else { + axis = C_y; + swap(cap.LogicalMin, cap.LogicalMax); + } + break; + case HID_USAGE_GENERIC_Z: + if (_device_class == DC_gamepad) { + axis = C_left_trigger; + } else if (_device_class == DC_flight_stick) { + axis = C_throttle; + } else { + axis = C_z; + swap(cap.LogicalMin, cap.LogicalMax); + } + break; + case HID_USAGE_GENERIC_RX: + if (_device_class == DC_gamepad) { + axis = C_right_x; + } else { + axis = C_pitch; + } + break; + case HID_USAGE_GENERIC_RY: + if (_device_class == DC_gamepad) { + axis = C_right_y; + } else { + axis = C_roll; + } + swap(cap.LogicalMin, cap.LogicalMax); + break; + case HID_USAGE_GENERIC_RZ: + if (_device_class == DC_gamepad) { + axis = C_right_trigger; + } else { + // Flip to match Panda's convention for heading. + axis = C_yaw; + swap(cap.LogicalMin, cap.LogicalMax); + } + break; + case HID_USAGE_GENERIC_SLIDER: + // Flip to match Panda's convention for heading. + axis = C_rudder; + swap(cap.LogicalMin, cap.LogicalMax); + break; + case HID_USAGE_GENERIC_WHEEL: + axis = C_wheel; + break; + case HID_USAGE_GENERIC_HATSWITCH: + // This is handled specially. + _hat_data_index = data_index; + _hat_data_minimum = cap.LogicalMin; + continue; + } + break; + } + + int control_index; + if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { + // T.Flight Hotas X throttle is reversed and can go backwards. + control_index = add_control(axis, cap.LogicalMax, cap.LogicalMin, true); + } else if (!is_signed) { + // All axes on the weird XInput-style mappings go from -1 to 1 + control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax, true); + } else { + control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax); + } + _indices[data_index] = Index::control(control_index, is_signed); + } + } + + // Do we need to emulate a hat switch or directional pad? + if (_hat_data_index != -1) { + _hat_left_button = (int)_buttons.size(); + if (_device_class == DC_gamepad) { + _buttons.push_back(ButtonState(GamepadButton::dpad_left())); + _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + _buttons.push_back(ButtonState(GamepadButton::dpad_up())); + } else { + _buttons.push_back(ButtonState(GamepadButton::hat_left())); + _buttons.push_back(ButtonState(GamepadButton::hat_right())); + _buttons.push_back(ButtonState(GamepadButton::hat_down())); + _buttons.push_back(ButtonState(GamepadButton::hat_up())); + } + } + + _max_data_count = _HidP_MaxDataListLength(HidP_Input, buffer); + + _handle = handle; + _is_connected = true; + return true; +} + +/** + * Called by InputDeviceManager when this device is removed. + */ +void WinRawInputDevice:: +on_removal() { + LightMutexHolder holder(_lock); + _is_connected = false; + _handle = nullptr; + if (_preparsed != nullptr) { + delete _preparsed; + _preparsed = nullptr; + } + _indices.clear(); + _report_buttons.clear(); +} + +void WinRawInputDevice:: +on_input(PRAWINPUT input) { + nassertv(input != nullptr); + nassertv(_preparsed != nullptr); + + BYTE *ptr = input->data.hid.bRawData; + if (input->data.hid.dwSizeHid == 0) { + return; + } + + PHIDP_DATA data = (PHIDP_DATA)alloca(sizeof(HIDP_DATA) * _max_data_count); + nassertv(data != nullptr); + ULONG count; + + LightMutexHolder holder(_lock); + + for (DWORD i = 0; i < input->data.hid.dwCount; ++i) { + // The first byte is the report identifier. We need it to figure out + // which buttons are off, since each report only contains the buttons that + // are "on". + UCHAR report_id = ptr[0]; + BitArray unset_buttons = _report_buttons[report_id]; + + count = _max_data_count; + NTSTATUS status = _HidP_GetData(HidP_Input, data, &count, (PHIDP_PREPARSED_DATA)_preparsed, (PCHAR)ptr, input->data.hid.dwSizeHid); + if (status == HIDP_STATUS_SUCCESS) { + for (ULONG di = 0; di < count; ++di) { + if (data[di].DataIndex != _hat_data_index) { + const Index &idx = _indices[data[di].DataIndex]; + if (idx._control >= 0) { + if (idx._signed) { + control_changed(idx._control, (SHORT)data[di].RawValue); + } else { + control_changed(idx._control, data[di].RawValue); + } + } + if (idx._button >= 0) { + unset_buttons.clear_bit(idx._button); + button_changed(idx._button, (data[di].On != FALSE)); + } + } else { + int value = (int)data[di].RawValue - _hat_data_minimum; + button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left + button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right + button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down + button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up + } + } + + // Now unset the buttons in this report that aren't pressed. + int button_index = unset_buttons.get_lowest_on_bit(); + while (button_index >= 0) { + button_changed(button_index, false); + unset_buttons.clear_bit(button_index); + button_index = unset_buttons.get_lowest_on_bit(); + } + } else if (device_cat.is_spam()) { + device_cat.spam() + << "Failed to get data from raw device " << _path + << " (error 0x" << hex << (status & 0xffffffffu) << dec << ")\n"; + } + + ptr += input->data.hid.dwSizeHid; + } +} + +/** + * Polls the input device for new activity, to ensure it contains the latest + * events. This will only have any effect for some types of input devices; + * others may be updated automatically, and this method will be a no-op. + */ +void WinRawInputDevice:: +do_poll() { +} + +#endif // _WIN32 diff --git a/panda/src/device/winRawInputDevice.h b/panda/src/device/winRawInputDevice.h new file mode 100644 index 0000000000..58090ed07f --- /dev/null +++ b/panda/src/device/winRawInputDevice.h @@ -0,0 +1,85 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file winRawInputDevice.h + * @author rdb + * @date 2018-01-19 + */ + +#ifndef WINRAWINPUTDEVICE_H +#define WINRAWINPUTDEVICE_H + +#include "pandabase.h" +#include "inputDevice.h" +#include "bitArray.h" + +#if defined(_WIN32) && !defined(CPPPARSER) + +class WinInputDeviceManager; + +/** + * This implementation of InputDevice uses the Win32 raw input API and the HID + * parser library to support a wide range of devices. + */ +class EXPCL_PANDA_DEVICE WinRawInputDevice FINAL : public InputDevice { +public: + WinRawInputDevice(WinInputDeviceManager *manager, const char *path); + ~WinRawInputDevice(); + + bool on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name); + void on_removal(); + void on_input(PRAWINPUT input); + +private: + virtual void do_poll(); + +private: + const string _path; + HANDLE _handle; + DWORD _size; + void *_preparsed; + ULONG _max_data_count; + ULONG _max_usage_count; + + // Indexed by report ID + pvector _report_buttons; + + // Either a button index or a control index. + struct Index { + Index() : _button(-1), _control(-1) {} + + static Index button(int index) { + Index idx; + idx._button = index; + return idx; + } + static Index control(int index, bool is_signed=true) { + Index idx; + idx._control = index; + idx._signed = is_signed; + return idx; + } + + int _button; + int _control; + bool _signed; + }; + + // Maps a "data index" to either button index or control index. + pvector _indices; + int _hat_data_index; + int _hat_data_minimum; + int _hat_left_button; + + WinInputDeviceManager *_manager; + friend class WinInputDeviceManager; +}; + +#endif // _WIN32 + +#endif diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 57fa73a416..5c8e9e8b2f 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -13,7 +13,8 @@ #include "xInputDevice.h" -#ifdef _WIN32 +#if defined(_WIN32) && !defined(CPPPARSER) + #include "gamepadButton.h" #include "inputDeviceManager.h" #include "string_utils.h" @@ -73,27 +74,44 @@ typedef struct _XINPUT_BATTERY_INFORMATION { } XINPUT_BATTERY_INFORMATION; // Undocumented, I figured out how this looks by trial and error. -struct XINPUT_BUSINFO { +typedef struct _XINPUT_BUSINFO { WORD VendorID; WORD ProductID; WORD RevisionID; WORD Unknown1; // Unknown - padding? DWORD InstanceID; DWORD Unknown2; - //WORD Unknown3; -}; + WORD Unknown3; +} XINPUT_BUSINFO; + +typedef struct _XINPUT_CAPABILITIES_EX { + BYTE Type; + BYTE SubType; + WORD Flags; + XINPUT_GAMEPAD Gamepad; + XINPUT_VIBRATION Vibration; + + // The following fields are undocumented. + WORD VendorID; + WORD ProductID; + WORD RevisionID; + WORD Unknown1; + WORD Unknown2; +} XINPUT_CAPABILITIES_EX; typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); typedef DWORD (*pXInputSetState)(DWORD, XINPUT_VIBRATION *); typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); +typedef DWORD (*pXInputGetCapabilitiesEx)(DWORD, DWORD, DWORD, XINPUT_CAPABILITIES_EX *); typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *); -static pXInputGetState get_state = NULL; -static pXInputSetState set_state = NULL; -static pXInputGetCapabilities get_capabilities = NULL; -static pXInputGetBatteryInformation get_battery_information = NULL; -static pXInputGetBaseBusInformation get_base_bus_information = NULL; +static pXInputGetState get_state = nullptr; +static pXInputSetState set_state = nullptr; +static pXInputGetCapabilities get_capabilities = nullptr; +static pXInputGetCapabilitiesEx get_capabilities_ex = nullptr; +static pXInputGetBatteryInformation get_battery_information = nullptr; +static pXInputGetBaseBusInformation get_base_bus_information = nullptr; bool XInputDevice::_initialized = false; @@ -108,26 +126,8 @@ XInputDevice(DWORD user_index) : nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT); - if (!_initialized) { - nassertv(init_xinput()); - } - - _name = "XInput Device #"; - _name += format_string(user_index); - _controls.resize(6); _buttons.resize(16); - - // Check if the device is connected. If so, initialize it. - XINPUT_CAPABILITIES caps; - XINPUT_STATE state; - if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS && - get_state(_index, &state) == ERROR_SUCCESS) { - _is_connected = true; - init_device(caps, state); - } else { - _is_connected = false; - } } /** @@ -138,17 +138,79 @@ XInputDevice:: do_set_vibration(0, 0); } +/** + * Called when a new input device arrives in the InputDeviceManager. This + * method checks whether it matches this XInput device. + */ +bool XInputDevice:: +check_arrival(const RID_DEVICE_INFO &info, DEVINST inst, + const string &name, const string &manufacturer) { + LightMutexHolder holder(_lock); + if (_is_connected) { + return false; + } + + if (!_initialized) { + nassertr_always(init_xinput(), false); + } + + XINPUT_CAPABILITIES_EX caps = {0}; + XINPUT_STATE state; + if ((get_capabilities_ex && get_capabilities_ex(1, _index, 0, &caps) != ERROR_SUCCESS) && + get_capabilities(_index, 0, (XINPUT_CAPABILITIES *)&caps) != ERROR_SUCCESS) { + return false; + } + + // Extra check for VID/PID if we have it, just to be sure. + if ((caps.VendorID != 0 && caps.VendorID != info.hid.dwVendorId) || + (caps.ProductID != 0 && caps.ProductID != info.hid.dwProductId)) { + return false; + } + + // Yes, take the name and manufacturer. + if (!name.empty()) { + _name = name; + } else { + _name = "XInput Device #"; + _name += format_string(_index + 1); + } + _manufacturer = manufacturer; + + if (inst && caps.ProductID == 0 && caps.RevisionID != 0) { + // XInput does not report a product ID for the Xbox 360 wireless adapter. + // Instead, we check that the RevisionID matches. + char buffer[4096]; + ULONG buflen = sizeof(buffer); + if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_HARDWAREID, 0, buffer, &buflen, 0) == CR_SUCCESS) { + string ids(buffer, buflen); + char revstr[16]; + sprintf(revstr, "REV_%04x", caps.RevisionID); + if (ids.find(revstr) == string::npos) { + return false; + } + } + } + + _is_connected = true; + init_device(caps, state); + _vendor_id = info.hid.dwVendorId; + _product_id = info.hid.dwProductId; + return true; +} + /** * Called periodically by the InputDeviceManager to detect whether the device * is currently connected. + * Returns true if the device wasn't connected, but now is. */ void XInputDevice:: detect(InputDeviceManager *mgr) { bool connected = false; - XINPUT_CAPABILITIES caps; + XINPUT_CAPABILITIES_EX caps = {0}; XINPUT_STATE state; - if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS && + if (((get_capabilities_ex && get_capabilities_ex(1, _index, 0, &caps) == ERROR_SUCCESS) || + get_capabilities(_index, 0, (XINPUT_CAPABILITIES *)&caps) == ERROR_SUCCESS) && get_state(_index, &state) == ERROR_SUCCESS) { connected = true; } else { @@ -203,9 +265,9 @@ init_xinput() { // Undocumented version (XInputGetStateEx) that includes a // state bit for the guide button. get_state = (pXInputGetState)GetProcAddress(module, MAKEINTRESOURCE(100)); - if (get_state == NULL) { + if (get_state == nullptr) { get_state = (pXInputGetState)GetProcAddress(module, "XInputGetState"); - if (get_state == NULL) { + if (get_state == nullptr) { device_cat.error() << "Failed to find function XInputGetState in " << dll_name << ".\n"; return false; @@ -213,14 +275,14 @@ init_xinput() { } set_state = (pXInputSetState)GetProcAddress(module, "XInputSetState"); - if (set_state == NULL) { + if (set_state == nullptr) { device_cat.error() << "Failed to find function XInputSetState in " << dll_name << ".\n"; return false; } get_capabilities = (pXInputGetCapabilities)GetProcAddress(module, "XInputGetCapabilities"); - if (get_capabilities == NULL) { + if (get_capabilities == nullptr) { device_cat.error() << "Failed to find function XInputGetCapabilities in " << dll_name << ".\n"; return false; @@ -228,6 +290,7 @@ init_xinput() { get_battery_information = (pXInputGetBatteryInformation)GetProcAddress(module, "XInputGetBatteryInformation"); get_base_bus_information = (pXInputGetBaseBusInformation)GetProcAddress(module, MAKEINTRESOURCE(104)); + get_capabilities_ex = (pXInputGetCapabilitiesEx)GetProcAddress(module, MAKEINTRESOURCE(108)); return true; } @@ -240,7 +303,8 @@ init_xinput() { * Initializes the device. Called when the device was just connected. */ void XInputDevice:: -init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { +init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { + nassertv(_initialized); // It seems that the Xbox One controller is reported as having a DevType of // zero, at least when I tested in with XInput 1.3 on Windows 7. //if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) { @@ -275,8 +339,8 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { set_control_map(1, C_throttle); set_control_map(2, C_roll); set_control_map(3, C_pitch); - set_control_map(4, C_hat_x); - set_control_map(5, C_hat_y); + set_control_map(4, C_none); + set_control_map(5, C_none); break; case XINPUT_DEVSUBTYPE_DANCE_PAD: @@ -327,11 +391,13 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { set_button_map(13, GamepadButton::action_x()); set_button_map(14, GamepadButton::action_y()); - if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) { + + if (caps.Vibration.wLeftMotorSpeed != 0 || + caps.Vibration.wRightMotorSpeed != 0) { _flags |= IDF_has_vibration; } - if (get_battery_information != NULL) { + if (get_battery_information != nullptr) { XINPUT_BATTERY_INFORMATION batt; if (get_battery_information(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) { if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED && @@ -344,54 +410,11 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { } } - // Get information about the USB device. - // This is not documented at all. I'm probably the first to try this. - XINPUT_BUSINFO businfo; - if (get_base_bus_information != NULL && - get_base_bus_information(0, &businfo) == ERROR_SUCCESS) { - _vendor_id = businfo.VendorID; - _product_id = businfo.ProductID; - - { - // Reformat the serial number into its original hex string form. - char sn[10]; - sprintf_s(sn, 10, "%08X", businfo.InstanceID); - _serial_number.assign(sn, 8); - } - - // Get information about the device from Windows. For that, we'll - // first need to construct the device path. Fortunately, we now have - // enough information to do so. - char path[32]; - sprintf_s(path, 32, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID); - - DEVINST inst; - if (CM_Locate_DevNodeA(&inst, path, 0) != 0) { - if (device_cat.is_debug()) { - device_cat.debug() - << "Could not locate device node " << path << "\n"; - } - } else { - // Get the device properties we need. - char buffer[4096]; - ULONG buflen = 4096; - if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_DEVICEDESC, 0, buffer, &buflen, 0) == CR_SUCCESS) { - _name.assign(buffer); - } - buflen = 4096; - if (CM_Get_DevNode_Registry_Property(inst, CM_DRP_MFG, 0, buffer, &buflen, 0) == CR_SUCCESS) { - _manufacturer.assign(buffer); - } - } - } - WORD buttons = state.Gamepad.wButtons; WORD mask = 1; for (int i = 0; i < 16; ++i) { - if (buttons & mask) { - // Set the state without triggering a button event. - _buttons[i].state = (buttons & mask) ? S_down : S_up; - } + // Set the state without triggering a button event. + _buttons[i].state = (buttons & mask) ? S_down : S_up; mask <<= 1; if (i == 10) { // XInput skips 0x0800. @@ -417,6 +440,8 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) { */ void XInputDevice:: do_set_vibration(double strong, double weak) { + nassertv_always(_is_connected); + XINPUT_VIBRATION vibration; vibration.wLeftMotorSpeed = strong * 0xffff; vibration.wRightMotorSpeed = weak * 0xffff; @@ -430,6 +455,11 @@ do_set_vibration(double strong, double weak) { */ void XInputDevice:: do_poll() { + // Not sure why someone would call this on a disconnected device. + if (!_is_connected) { + return; + } + XINPUT_STATE state; if (get_state(_index, &state) != ERROR_SUCCESS) { @@ -440,18 +470,6 @@ do_poll() { mgr->remove_device(this); } return; - - } else if (!_is_connected) { - // Device was (re)connected. It's a bit strange to call poll() on a - // disconnected device, but there's nothing stopping the user from - // doing so. - XINPUT_CAPABILITIES caps; - if (get_capabilities(_index, 0, &caps) == ERROR_SUCCESS) { - _is_connected = true; - init_device(caps, state); - InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); - mgr->add_device(this); - } } if (state.dwPacketNumber == _last_packet) { diff --git a/panda/src/device/xInputDevice.h b/panda/src/device/xInputDevice.h index 73d8c8747d..598cd5ec74 100644 --- a/panda/src/device/xInputDevice.h +++ b/panda/src/device/xInputDevice.h @@ -17,13 +17,17 @@ #include "pandabase.h" #include "inputDevice.h" -#ifdef _WIN32 +#if defined(_WIN32) && !defined(CPPPARSER) + +#include class InputDeviceManager; -typedef struct _XINPUT_CAPABILITIES XINPUT_CAPABILITIES; +typedef struct _XINPUT_CAPABILITIES_EX XINPUT_CAPABILITIES_EX; typedef struct _XINPUT_STATE XINPUT_STATE; +typedef struct tagRID_DEVICE_INFO RID_DEVICE_INFO; + /** * This implementation of InputDevice uses Microsoft's XInput library to * interface with an Xbox 360 game controller. @@ -33,11 +37,13 @@ public: XInputDevice(DWORD user_index); ~XInputDevice(); + bool check_arrival(const RID_DEVICE_INFO &info, DEVINST inst, + const string &name, const string &manufacturer); void detect(InputDeviceManager *mgr); static bool init_xinput(); private: - void init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state); + void init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state); virtual void do_set_vibration(double strong, double weak); virtual void do_poll(); From 712b246dd6839f5423db0c20c94ea26df5cae19b Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 23 Jan 2018 12:56:42 +0100 Subject: [PATCH 197/424] input: reverse rudder direction to match yaw, reverse 3D mouse roll Although having the rudder reversed is less intuitive, this makes it match the Panda convention for "heading". --- panda/src/device/evdevInputDevice.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index cf20e57261..115ce8ee77 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -518,9 +518,9 @@ init_device() { // We'd like to reverse the Y axis to match the XInput behavior. // Also reverse the yaw axis to match right-hand coordinate system. // Also T.Flight Hotas X throttle is reversed and can go backwards. - if (axis == C_yaw || axis == C_left_y || axis == C_right_y || + if (axis == C_yaw || axis == C_rudder || axis == C_left_y || axis == C_right_y || (axis == C_throttle && (quirks & QB_reversed_throttle) != 0) || - (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z))) { + (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z || axis == C_roll))) { swap(absinfo.maximum, absinfo.minimum); } if (axis == C_throttle && (quirks & QB_centered_throttle) != 0) { From f189b817c6d5377e1bf81f7fc55d560b3d3057f2 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 23 Jan 2018 12:59:14 +0100 Subject: [PATCH 198/424] input: fix name/manufacturer/serial fetching for some evdev devices --- panda/src/device/evdevInputDevice.cxx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 115ce8ee77..6855685084 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -572,8 +572,14 @@ init_device() { char path[64]; char buffer[256]; + const char *parent = ""; sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); FILE *f = fopen(path, "r"); + if (!f) { + parent = "../"; + sprintf(path, "/sys/class/input/event%d/device/device/%s../product", _index, parent); + f = fopen(path, "r"); + } if (f) { if (fgets(buffer, sizeof(buffer), f) != NULL) { buffer[strcspn(buffer, "\r\n")] = 0; @@ -583,7 +589,7 @@ init_device() { } fclose(f); } - sprintf(path, "/sys/class/input/event%d/device/device/../manufacturer", _index); + sprintf(path, "/sys/class/input/event%d/device/device/%s../manufacturer", _index, parent); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != NULL) { @@ -592,7 +598,7 @@ init_device() { } fclose(f); } - sprintf(path, "/sys/class/input/event%d/device/device/../serial", _index); + sprintf(path, "/sys/class/input/event%d/device/device/%s../serial", _index, parent); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != NULL) { From 3da32b7b7cd00ba5dfe9abbe251848665d105c61 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 25 Jan 2018 20:14:35 +0100 Subject: [PATCH 199/424] input: split out LinuxInputDeviceManager from InputDeviceManager --- panda/src/device/evdevInputDevice.cxx | 10 +- panda/src/device/evdevInputDevice.h | 9 +- panda/src/device/inputDeviceManager.cxx | 283 +----------------- panda/src/device/inputDeviceManager.h | 3 - panda/src/device/linuxInputDeviceManager.cxx | 294 +++++++++++++++++++ panda/src/device/linuxInputDeviceManager.h | 45 +++ panda/src/device/linuxJoystickDevice.cxx | 9 +- panda/src/device/linuxJoystickDevice.h | 8 +- panda/src/device/p3device_composite1.cxx | 1 + panda/src/x11display/x11GraphicsWindow.cxx | 2 +- 10 files changed, 371 insertions(+), 293 deletions(-) create mode 100644 panda/src/device/linuxInputDeviceManager.cxx create mode 100644 panda/src/device/linuxInputDeviceManager.h diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 6855685084..a4bdc43806 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -18,7 +18,7 @@ #include "gamepadButton.h" #include "keyboardButton.h" #include "mouseButton.h" -#include "inputDeviceManager.h" +#include "linuxInputDeviceManager.h" #include #include @@ -79,7 +79,8 @@ TypeHandle EvdevInputDevice::_type_handle; * Creates a new device representing the evdev device with the given index. */ EvdevInputDevice:: -EvdevInputDevice(int index) : +EvdevInputDevice(LinuxInputDeviceManager *manager, int index) : + _manager(manager), _index(index), _fd(-1), _can_write(false), @@ -203,8 +204,9 @@ do_poll() { // If we got events, we are obviously connected. Mark us so. if (!_is_connected) { _is_connected = true; - InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); - mgr->add_device(this); + if (_manager != nullptr) { + _manager->add_device(this); + } } } } diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 0a6baa058a..8f73d04a20 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -18,6 +18,8 @@ #ifdef PHAVE_LINUX_INPUT_H +class LinuxInputDeviceManager; + /** * This is a type of device that uses the Linux /dev/input/event# API to read * data from a raw mouse or other input device. Unlike the joystick API, the @@ -25,7 +27,7 @@ */ class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { public: - EvdevInputDevice(int index); + EvdevInputDevice(LinuxInputDeviceManager *manager, int index); virtual ~EvdevInputDevice(); private: @@ -36,8 +38,11 @@ private: bool process_events(); private: - int _index; + LinuxInputDeviceManager *_manager; + int _fd; + int _index; + bool _can_write; int _ff_id; bool _ff_playing; diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 31f7f93934..a4ee1c4c8e 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -13,17 +13,10 @@ #include "inputDeviceManager.h" #include "ioKitInputDevice.h" -#include "linuxJoystickDevice.h" +#include "linuxInputDeviceManager.h" #include "winInputDeviceManager.h" #include "throw_event.h" -#ifdef PHAVE_LINUX_INPUT_H -#include -#include -#include -#include -#endif - InputDeviceManager *InputDeviceManager::_global_ptr = NULL; /** @@ -31,51 +24,7 @@ InputDeviceManager *InputDeviceManager::_global_ptr = NULL; * connected and setting up any platform-dependent structures necessary for * listening for future device connect events. */ -#ifdef PHAVE_LINUX_INPUT_H -InputDeviceManager:: -InputDeviceManager() : _inotify_fd(-1) { - // Use inotify to watch /dev/input for hotplugging of devices. - _inotify_fd = inotify_init1(O_NONBLOCK); - - if (_inotify_fd < 0) { - device_cat.error() - << "Error initializing inotify: " << strerror(errno) << "\n"; - - } else if (inotify_add_watch(_inotify_fd, "/dev/input", IN_CREATE | IN_ATTRIB | IN_DELETE) < 0) { - device_cat.error() - << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; - } - - // Scan /dev/input for a list of input devices. - DIR *dir = opendir("/dev/input"); - if (dir) { - vector_int indices; - dirent *entry = readdir(dir); - while (entry != NULL) { - int index = -1; - if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) { - indices.push_back(index); - } - entry = readdir(dir); - } - closedir(dir); - - // We'll want to sort the devices by index, since the order may be - // meaningful (eg. for the Xbox wireless receiver). - std::sort(indices.begin(), indices.end()); - _evdev_devices.resize(indices.back() + 1, NULL); - - vector_int::const_iterator it; - for (it = indices.begin(); it != indices.end(); ++it) { - consider_add_evdev_device(*it); - } - } else { - device_cat.error() - << "Error opening directory /dev/input: " << strerror(errno) << "\n"; - return; - } -} -#elif defined(__APPLE__) +#if defined(__APPLE__) InputDeviceManager:: InputDeviceManager() { _hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); @@ -128,12 +77,7 @@ InputDeviceManager() : _lock("InputDeviceManager") { */ InputDeviceManager:: ~InputDeviceManager() { -#ifdef PHAVE_LINUX_INPUT_H - if (_inotify_fd >= 0) { - close(_inotify_fd); - _inotify_fd = -1; - } -#elif defined(__APPLE__) +#if defined(__APPLE__) IOHIDManagerUnscheduleFromRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); IOHIDManagerClose(_hid_manager, kIOHIDOptionsTypeNone); CFRelease(_hid_manager); @@ -147,129 +91,13 @@ void InputDeviceManager:: make_global_ptr() { #ifdef _WIN32 _global_ptr = new WinInputDeviceManager; +#elif defined(PHAVE_LINUX_INPUT_H) + _global_ptr = new LinuxInputDeviceManager; #else _global_ptr = new InputDeviceManager; #endif } -#ifdef PHAVE_LINUX_INPUT_H -/** - * Checks whether the event device with the given index is accessible, and if - * so, adds it. Returns the device if it was newly connected. - * - * This is the preferred interface for input devices on Linux. - */ -InputDevice *InputDeviceManager:: -consider_add_evdev_device(int ev_index) { - if (ev_index < _evdev_devices.size()) { - if (_evdev_devices[ev_index] != NULL) { - // We already have this device. FIXME: probe it and add it to the - // list of connected devices? - return NULL; - } - } else { - // Make room to store this index. - _evdev_devices.resize(ev_index + 1, NULL); - } - - // Check if we can directly read the event device. - char path[64]; - sprintf(path, "/dev/input/event%d", ev_index); - - if (access(path, R_OK) == 0) { - PT(InputDevice) device = new EvdevInputDevice(ev_index); - if (device_cat.is_debug()) { - device_cat.debug() - << "Discovered evdev input device " << *device << "\n"; - } - - _evdev_devices[ev_index] = device; - - if (device->is_connected()) { - _connected_devices.add_device(MOVE(device)); - } else { - // Wait for activity on the device before it is considered connected. - _inactive_devices.add_device(MOVE(device)); - } - return _evdev_devices[ev_index]; - } - - // Nope. The permissions haven't been configured to allow it. Check if this - // corresponds to a /dev/input/jsX interface, which has a better chance of - // having read permissions set, but doesn't export all of the features - // (notably, force feedback). - - // We do this by checking for a js# directory inside the sysfs directory. - sprintf(path, "/sys/class/input/event%d/device", ev_index); - - DIR *dir = opendir(path); - if (dir == NULL) { - if (device_cat.is_debug()) { - device_cat.debug() - << "Error opening directory " << path << ": " << strerror(errno) << "\n"; - } - return NULL; - } - - dirent *entry = readdir(dir); - while (entry != NULL) { - int js_index = -1; - if (sscanf(entry->d_name, "js%d", &js_index) == 1) { - // Yes, we fonud a corresponding js device. Try adding it. - closedir(dir); - - InputDevice *device = consider_add_js_device(js_index); - if (device != NULL && device_cat.is_warning()) { - // This seemed to work. Display a warning to the user indicating - // that they might want to configure udev properly. - device_cat.warning() - << "/dev/input/event" << ev_index << " is not readable, some " - "features will be unavailable.\n"; - } - _evdev_devices[ev_index] = device; - return device; - } - entry = readdir(dir); - } - - closedir(dir); - return NULL; -} - -/** - * Checks whether the joystick device with the given index is accessible, and - * if so, adds it. Returns the device if it was newly connected. - * - * This is only used on Linux as a fallback interface for when an evdev device - * cannot be accessed. - */ -InputDevice *InputDeviceManager:: -consider_add_js_device(int js_index) { - char path[64]; - sprintf(path, "/dev/input/js%d", js_index); - - if (access(path, R_OK) == 0) { - PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(js_index); - if (device_cat.is_debug()) { - device_cat.debug() - << "Discovered joydev input device " << *device << "\n"; - } - InputDevice *device_p = device.p(); - - if (device->is_connected()) { - _connected_devices.add_device(MOVE(device)); - } else { - // Wait for activity on the device before it is considered connected. - _inactive_devices.add_device(MOVE(device)); - } - return device_p; - } - - return NULL; -} - -#endif - /** * Description: Returns all currently connected devices. */ @@ -348,105 +176,4 @@ remove_device(InputDevice *device) { */ void InputDeviceManager:: update() { -#ifdef PHAVE_LINUX_INPUT_H - // Check for any devices that may be disconnected and need to be probed in - // order to see whether they have been reconnected. - InputDeviceSet inactive_devices; - { - LightMutexHolder holder(_lock); - inactive_devices = _inactive_devices; - } - for (size_t i = 0; i < inactive_devices.size(); ++i) { - InputDevice *device = inactive_devices[i]; - if (device != NULL && !device->is_connected()) { - device->poll(); - } - } - - // We use inotify to tell us whether a device was added, removed, or has - // changed permissions to allow us to access it. - unsigned int avail = 0; - ioctl(_inotify_fd, FIONREAD, &avail); - if (avail == 0) { - return; - } - - char buffer[avail]; - int n_read = read(_inotify_fd, buffer, avail); - if (n_read < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - // No data available for now. - - } else { - device_cat.error() << "read: " << strerror(errno) << "\n"; - } - return; - } - - LightMutexHolder holder(_lock); - - // Iterate over the events in the buffer. - char *ptr = buffer; - char *end = buffer + avail; - while (ptr < end) { - inotify_event *event = (inotify_event *)ptr; - - string name(event->name); - - if (event->mask & IN_DELETE) { - // The device was deleted. If we have it, remove it. - - int index = -1; - if (sscanf(event->name, "event%d", &index) == 1) { - // Check if we have this evdev device. If so, disconnect it. - if (index < _evdev_devices.size()) { - PT(InputDevice) device = _evdev_devices[index]; - if (device != NULL) { - _evdev_devices[index] = NULL; - _inactive_devices.remove_device(device); - if (_connected_devices.remove_device(device)) { - throw_event("disconnect-device", device.p()); - } - - if (device_cat.is_debug()) { - device_cat.debug() - << "Removed input device " << *device << "\n"; - } - } - } - } - - } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { - // The device was created, or it was chmodded to be accessible. We need - // to check for the latter since it seems that the device may get the - // IN_CREATE event before the driver gets the permissions set properly. - - int index = -1; - if (sscanf(event->name, "event%d", &index) == 1) { - InputDevice *device = consider_add_evdev_device(index); - if (device != NULL && device->is_connected()) { - throw_event("connect-device", device); - } - } - } - - ptr += sizeof(inotify_event) + event->len; - } -#endif } - -#if defined(__APPLE__) && !defined(CPPPARSER) -void InputDeviceManager:: -on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) { - InputDeviceManager *mgr = (InputDeviceManager *)ctx; - nassertv(mgr != nullptr); - nassertv(device); - - PT(InputDevice) input_device = new IOKitInputDevice(device); - if (device_cat.is_debug()) { - device_cat.debug() - << "Discovered input device " << *input_device << "\n"; - } - mgr->add_device(input_device); -} -#endif diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index c6c50a33d9..5f827cd3a9 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -62,9 +62,6 @@ protected: LightMutex _lock; #ifdef PHAVE_LINUX_INPUT_H - int _inotify_fd; - - pvector _evdev_devices; InputDeviceSet _inactive_devices; #endif diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx new file mode 100644 index 0000000000..61eed54b42 --- /dev/null +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -0,0 +1,294 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file linuxInputDeviceManager.cxx + * @author rdb + * @date 2018-01-25 + */ + +#include "inputDeviceManager.h" +#include "throw_event.h" + +#ifdef PHAVE_LINUX_INPUT_H + +#include "evdevInputDevice.h" +#include "linuxJoystickDevice.h" + +#include +#include +#include +#include + +/** + * Initializes the device manager by scanning which devices are currently + * connected and setting up any platform-dependent structures necessary for + * listening for future device connect events. + */ +LinuxInputDeviceManager:: +LinuxInputDeviceManager() { + // Use inotify to watch /dev/input for hotplugging of devices. + _inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + + if (_inotify_fd < 0) { + device_cat.error() + << "Error initializing inotify: " << strerror(errno) << "\n"; + + } else if (inotify_add_watch(_inotify_fd, "/dev/input", IN_CREATE | IN_ATTRIB | IN_DELETE) < 0) { + device_cat.error() + << "Error adding inotify watch on /dev/input: " << strerror(errno) << "\n"; + } + + // Scan /dev/input for a list of input devices. + DIR *dir = opendir("/dev/input"); + if (dir) { + vector_int indices; + dirent *entry = readdir(dir); + while (entry != nullptr) { + int index = -1; + if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) { + indices.push_back(index); + } + entry = readdir(dir); + } + closedir(dir); + + // We'll want to sort the devices by index, since the order may be + // meaningful (eg. for the Xbox wireless receiver). + std::sort(indices.begin(), indices.end()); + _evdev_devices.resize(indices.back() + 1, nullptr); + + vector_int::const_iterator it; + for (it = indices.begin(); it != indices.end(); ++it) { + consider_add_evdev_device(*it); + } + } else { + device_cat.error() + << "Error opening directory /dev/input: " << strerror(errno) << "\n"; + return; + } +} + +/** + * Closes any resources that the device manager was using to listen for events. + */ +LinuxInputDeviceManager:: +~LinuxInputDeviceManager() { + if (_inotify_fd >= 0) { + close(_inotify_fd); + _inotify_fd = -1; + } +} + +/** + * Checks whether the event device with the given index is accessible, and if + * so, adds it. Returns the device if it was newly connected. + * + * This is the preferred interface for input devices on Linux. + */ +InputDevice *LinuxInputDeviceManager:: +consider_add_evdev_device(int ev_index) { + if (ev_index < _evdev_devices.size()) { + if (_evdev_devices[ev_index] != nullptr) { + // We already have this device. FIXME: probe it and add it to the + // list of connected devices? + return nullptr; + } + } else { + // Make room to store this index. + _evdev_devices.resize(ev_index + 1, nullptr); + } + + // Check if we can directly read the event device. + char path[64]; + sprintf(path, "/dev/input/event%d", ev_index); + + if (access(path, R_OK) == 0) { + PT(InputDevice) device = new EvdevInputDevice(this, ev_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered evdev input device " << *device << "\n"; + } + + _evdev_devices[ev_index] = device; + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return _evdev_devices[ev_index]; + } + + // Nope. The permissions haven't been configured to allow it. Check if this + // corresponds to a /dev/input/jsX interface, which has a better chance of + // having read permissions set, but doesn't export all of the features + // (notably, force feedback). + + // We do this by checking for a js# directory inside the sysfs directory. + sprintf(path, "/sys/class/input/event%d/device", ev_index); + + DIR *dir = opendir(path); + if (dir == nullptr) { + if (device_cat.is_debug()) { + device_cat.debug() + << "Error opening directory " << path << ": " << strerror(errno) << "\n"; + } + return nullptr; + } + + dirent *entry = readdir(dir); + while (entry != nullptr) { + int js_index = -1; + if (sscanf(entry->d_name, "js%d", &js_index) == 1) { + // Yes, we fonud a corresponding js device. Try adding it. + closedir(dir); + + InputDevice *device = consider_add_js_device(js_index); + if (device != nullptr && device_cat.is_warning()) { + // This seemed to work. Display a warning to the user indicating + // that they might want to configure udev properly. + device_cat.warning() + << "/dev/input/event" << ev_index << " is not readable, some " + "features will be unavailable.\n"; + } + _evdev_devices[ev_index] = device; + return device; + } + entry = readdir(dir); + } + + closedir(dir); + return nullptr; +} + +/** + * Checks whether the joystick device with the given index is accessible, and + * if so, adds it. Returns the device if it was newly connected. + * + * This is only used on Linux as a fallback interface for when an evdev device + * cannot be accessed. + */ +InputDevice *LinuxInputDeviceManager:: +consider_add_js_device(int js_index) { + char path[64]; + sprintf(path, "/dev/input/js%d", js_index); + + if (access(path, R_OK) == 0) { + PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(this, js_index); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered joydev input device " << *device << "\n"; + } + InputDevice *device_p = device.p(); + + if (device->is_connected()) { + _connected_devices.add_device(MOVE(device)); + } else { + // Wait for activity on the device before it is considered connected. + _inactive_devices.add_device(MOVE(device)); + } + return device_p; + } + + return nullptr; +} + +/** + * Polls the system to see if there are any new devices. In some + * implementations this is a no-op. + */ +void LinuxInputDeviceManager:: +update() { + // Check for any devices that may be disconnected and need to be probed in + // order to see whether they have been reconnected. + InputDeviceSet inactive_devices; + { + LightMutexHolder holder(_lock); + inactive_devices = _inactive_devices; + } + for (size_t i = 0; i < inactive_devices.size(); ++i) { + InputDevice *device = inactive_devices[i]; + if (device != nullptr && !device->is_connected()) { + device->poll(); + } + } + + // We use inotify to tell us whether a device was added, removed, or has + // changed permissions to allow us to access it. + unsigned int avail = 0; + ioctl(_inotify_fd, FIONREAD, &avail); + if (avail == 0) { + return; + } + + char buffer[avail]; + int n_read = read(_inotify_fd, buffer, avail); + if (n_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // No data available for now. + + } else { + device_cat.error() << "read: " << strerror(errno) << "\n"; + } + return; + } + + LightMutexHolder holder(_lock); + + // Iterate over the events in the buffer. + char *ptr = buffer; + char *end = buffer + avail; + while (ptr < end) { + inotify_event *event = (inotify_event *)ptr; + + string name(event->name); + + if (event->mask & IN_DELETE) { + // The device was deleted. If we have it, remove it. + + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + // Check if we have this evdev device. If so, disconnect it. + if (index < _evdev_devices.size()) { + PT(InputDevice) device = _evdev_devices[index]; + if (device != nullptr) { + device->set_connected(false); + _evdev_devices[index] = nullptr; + _inactive_devices.remove_device(device); + if (_connected_devices.remove_device(device)) { + throw_event("disconnect-device", device.p()); + } + + if (device_cat.is_debug()) { + device_cat.debug() + << "Removed input device " << *device << "\n"; + } + } + } + } + + } else if (event->mask & (IN_CREATE | IN_ATTRIB)) { + // The device was created, or it was chmodded to be accessible. We need + // to check for the latter since it seems that the device may get the + // IN_CREATE event before the driver gets the permissions set properly. + + int index = -1; + if (sscanf(event->name, "event%d", &index) == 1) { + InputDevice *device = consider_add_evdev_device(index); + if (device != nullptr && device->is_connected()) { + throw_event("connect-device", device); + } + } + } + + ptr += sizeof(inotify_event) + event->len; + } +} + +#endif // PHAVE_LINUX_INPUT_H diff --git a/panda/src/device/linuxInputDeviceManager.h b/panda/src/device/linuxInputDeviceManager.h new file mode 100644 index 0000000000..fd82efc689 --- /dev/null +++ b/panda/src/device/linuxInputDeviceManager.h @@ -0,0 +1,45 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file linuxInputDeviceManager.h + * @author rdb + * @date 2018-01-25 + */ + +#ifndef LINUXINPUTDEVICEMANAGER_H +#define LINUXINPUTDEVICEMANAGER_H + +#include "inputDeviceManager.h" + +#ifdef PHAVE_LINUX_INPUT_H + +/** + * This class keeps track of all the devices on a system, and sends out events + * when a device has been hot-plugged. + */ +class EXPCL_PANDA_DEVICE LinuxInputDeviceManager FINAL : public InputDeviceManager { +private: + LinuxInputDeviceManager(); + ~LinuxInputDeviceManager(); + + InputDevice *consider_add_evdev_device(int index); + InputDevice *consider_add_js_device(int index); + + virtual void update(); + +protected: + int _inotify_fd; + + pvector _evdev_devices; + + friend class InputDeviceManager; +}; + +#endif // PHAVE_LINUX_INPUT_H + +#endif diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index f510e02f6a..d3fea162bd 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -17,6 +17,7 @@ #ifdef PHAVE_LINUX_INPUT_H #include "gamepadButton.h" +#include "linuxInputDeviceManager.h" #include #include @@ -27,7 +28,8 @@ TypeHandle LinuxJoystickDevice::_type_handle; * Creates a new device using the Linux joystick device with the given index. */ LinuxJoystickDevice:: -LinuxJoystickDevice(int index) : +LinuxJoystickDevice(LinuxInputDeviceManager *manager, int index) : + _manager(manager), _fd(-1), _index(index), _dpad_x_axis(-1), @@ -79,8 +81,9 @@ do_poll() { // If we got events, we are obviously connected. Mark us so. if (!_is_connected) { _is_connected = true; - InputDeviceManager *mgr = InputDeviceManager::get_global_ptr(); - mgr->add_device(this); + if (_manager != nullptr) { + _manager->add_device(this); + } } } } diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index ab97dd5293..fad69730c1 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -18,13 +18,15 @@ #ifdef PHAVE_LINUX_INPUT_H +class LinuxInputDeviceManager; + /** * This is a type of device that uses the Linux /dev/input/js# API to read * data from a game controller. */ class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice { -public: - LinuxJoystickDevice(int index); +PUBLISHED: + LinuxJoystickDevice(LinuxInputDeviceManager *manager, int index); virtual ~LinuxJoystickDevice(); bool check_events() const; @@ -36,6 +38,8 @@ private: bool process_events(); private: + LinuxInputDeviceManager *_manager; + int _fd; int _index; diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 65a03ccc48..0067579474 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -11,6 +11,7 @@ #include "inputDeviceNode.cxx" #include "inputDeviceSet.cxx" #include "ioKitInputDevice.cxx" +#include "linuxInputDeviceManager.cxx" #include "linuxJoystickDevice.cxx" #include "winInputDeviceManager.cxx" #include "winRawInputDevice.cxx" diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index a0bd1013a4..8e4776e292 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -1245,7 +1245,7 @@ open_raw_mice() { string fn = fnb.str(); int fd = open(fn.c_str(), O_RDONLY | O_NONBLOCK, 0); if (fd >= 0) { - EvdevInputDevice *device = new EvdevInputDevice(fd); + EvdevInputDevice *device = new EvdevInputDevice(nullptr, fd); nassertd(device != NULL) continue; if (device->has_pointer()) { From 6e476e77a3be854681162ade2488496eed6ff1ca Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 28 Jan 2018 10:58:39 +0100 Subject: [PATCH 200/424] input: support imitation SNES USB gamepad on Windows --- panda/src/device/winInputDeviceManager.cxx | 9 +++- panda/src/device/winRawInputDevice.cxx | 53 ++++++++++++++++------ 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/panda/src/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx index 124229b5ea..8d3d0c769a 100644 --- a/panda/src/device/winInputDeviceManager.cxx +++ b/panda/src/device/winInputDeviceManager.cxx @@ -245,8 +245,15 @@ on_input_device_arrival(HANDLE handle) { if (dev_class == "HIDClass" && _CM_Get_DevNode_PropertyW != nullptr && _CM_Get_DevNode_PropertyW(cur, &bus_reported_device_desc, &type, (PBYTE)buffer, &buflen, 0) == CR_SUCCESS && type == DEVPROP_TYPE_STRING) { + + // Some devices insert quite some trailing space here. + wchar_t *wbuffer = (wchar_t *)buffer; + size_t wlen = wcslen(wbuffer); + while (iswspace(wbuffer[wlen - 1])) { + wbuffer[--wlen] = 0; + } TextEncoder encoder; - name.assign(encoder.encode_wtext((wchar_t *)buffer)); + name.assign(encoder.encode_wtext(wstring(wbuffer, wlen))); break; } else { buflen = 4096; diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index 68e8e89bf3..391140eb31 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -265,6 +265,11 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name) { info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) { _device_class = DC_flight_stick; + if (_name == "usb gamepad") { + // Well, it claims to be a gamepad... + _device_class = DC_gamepad; + } + // Mice } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) { @@ -320,6 +325,39 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name) { return false; } + // Do we have a button mapping? + static const ButtonHandle gamepad_buttons_common[] = { + ButtonHandle::none(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::start(), + GamepadButton::back(), + GamepadButton::lstick(), + GamepadButton::rstick(), + }; + const ButtonHandle *gamepad_buttons = gamepad_buttons_common; + if (_vendor_id == 0x0810 && _product_id == 0xe501) { + // SNES-style USB gamepad + static const ButtonHandle gamepad_buttons_snes[] = { + ButtonHandle::none(), + GamepadButton::action_x(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + ButtonHandle::none(), + ButtonHandle::none(), + GamepadButton::back(), + GamepadButton::start(), + }; + gamepad_buttons = gamepad_buttons_snes; + } + // Prepare a mapping of data indices to button/control indices. _indices.resize(caps.NumberInputDataIndices); @@ -369,20 +407,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, string name) { switch (cap.UsagePage) { case HID_USAGE_PAGE_BUTTON: if (_device_class == DC_gamepad) { - static const ButtonHandle gamepad_buttons[] = { - ButtonHandle::none(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_x(), - GamepadButton::action_y(), - GamepadButton::lshoulder(), - GamepadButton::rshoulder(), - GamepadButton::start(), - GamepadButton::back(), - GamepadButton::lstick(), - GamepadButton::rstick(), - }; - if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + if (usage < sizeof(gamepad_buttons_common) / sizeof(ButtonHandle)) { handle = gamepad_buttons[usage]; } } else if (_device_class == DC_flight_stick) { From 9e6d0d91adfac589fa4b602ead47e6ecc42abc2d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 4 Feb 2018 10:58:36 +0100 Subject: [PATCH 201/424] input: fixes for older Android API levels (thanks to pmp-p) --- panda/src/device/evdevInputDevice.cxx | 21 ++++++++++++++------ panda/src/device/linuxInputDeviceManager.cxx | 5 +++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index a4bdc43806..cae448c1f3 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -23,6 +23,15 @@ #include #include +// Android introduces these in API level 21. +#ifndef BTN_TRIGGER_HAPPY +#define BTN_TRIGGER_HAPPY 0x2c0 +#define BTN_TRIGGER_HAPPY1 0x2c0 +#define BTN_TRIGGER_HAPPY2 0x2c1 +#define BTN_TRIGGER_HAPPY3 0x2c2 +#define BTN_TRIGGER_HAPPY4 0x2c3 +#endif + #define test_bit(bit, array) ((array)[(bit)>>3] & (1<<((bit)&7))) enum QuirkBits { @@ -220,7 +229,7 @@ init_device() { LightMutexHolder holder(_lock); - uint8_t evtypes[(EV_CNT + 7) >> 3]; + uint8_t evtypes[(EV_MAX + 8) >> 3]; memset(evtypes, 0, sizeof(evtypes)); char name[128]; if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || @@ -248,7 +257,7 @@ init_device() { bool has_keys = false; bool has_axes = false; - uint8_t keys[(KEY_CNT + 7) >> 3]; + uint8_t keys[(KEY_MAX + 8) >> 3]; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. memset(keys, 0, sizeof(keys)); @@ -261,7 +270,7 @@ init_device() { } int num_bits = 0; - uint8_t axes[(ABS_CNT + 7) >> 3]; + uint8_t axes[(ABS_MAX + 8) >> 3]; if (test_bit(EV_ABS, evtypes)) { // Check which axes are on the device. memset(axes, 0, sizeof(axes)); @@ -355,11 +364,11 @@ init_device() { if (has_keys) { // Also check whether the buttons are currently pressed. - uint8_t states[(KEY_CNT + 7) >> 3]; + uint8_t states[(KEY_MAX + 8) >> 3]; memset(states, 0, sizeof(states)); ioctl(_fd, EVIOCGKEY(sizeof(states)), states); - for (int i = 0; i < KEY_CNT; ++i) { + for (int i = 0; i <= KEY_MAX; ++i) { if (test_bit(i, keys)) { ButtonState button; button.handle = map_button(i, _device_class); @@ -546,7 +555,7 @@ init_device() { } if (test_bit(EV_FF, evtypes)) { - uint8_t effects[(FF_CNT + 7) >> 3]; + uint8_t effects[(FF_MAX + 8) >> 3]; memset(effects, 0, sizeof(effects)); ioctl(_fd, EVIOCGBIT(EV_FF, sizeof(effects)), effects); diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 61eed54b42..8278edd226 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -32,7 +32,12 @@ LinuxInputDeviceManager:: LinuxInputDeviceManager() { // Use inotify to watch /dev/input for hotplugging of devices. +#if !defined(__ANDROID_API__) || __ANDROID_API__ >= 21 _inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); +#else + _inotify_fd = inotify_init(); + fcntl(_inotify_fd, O_NONBLOCK | O_CLOEXEC); +#endif if (_inotify_fd < 0) { device_cat.error() From 6ada306184eb17f6bfd9d3d5bcf729aa38cc8805 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 4 Feb 2018 11:00:43 +0100 Subject: [PATCH 202/424] input: split out macOS implementation of device manager --- panda/src/device/inputDeviceManager.cxx | 62 +------------ panda/src/device/inputDeviceManager.h | 16 ---- panda/src/device/ioKitInputDeviceManager.cxx | 93 ++++++++++++++++++++ panda/src/device/ioKitInputDeviceManager.h | 40 +++++++++ panda/src/device/p3device_composite1.cxx | 1 + 5 files changed, 138 insertions(+), 74 deletions(-) create mode 100644 panda/src/device/ioKitInputDeviceManager.cxx create mode 100644 panda/src/device/ioKitInputDeviceManager.h diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index a4ee1c4c8e..140b4379f1 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -12,77 +12,21 @@ */ #include "inputDeviceManager.h" -#include "ioKitInputDevice.h" +#include "ioKitInputDeviceManager.h" #include "linuxInputDeviceManager.h" #include "winInputDeviceManager.h" #include "throw_event.h" -InputDeviceManager *InputDeviceManager::_global_ptr = NULL; +InputDeviceManager *InputDeviceManager::_global_ptr = nullptr; /** * Initializes the input device manager by scanning which devices are currently * connected and setting up any platform-dependent structures necessary for * listening for future device connect events. */ -#if defined(__APPLE__) -InputDeviceManager:: -InputDeviceManager() { - _hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - if (!_hid_manager) { - device_cat.error() - << "Failed to create an IOHIDManager.\n"; - return; - } - - // The types of devices we're interested in. - int page = kHIDPage_GenericDesktop; - int usages[] = {kHIDUsage_GD_GamePad, - kHIDUsage_GD_Joystick, - kHIDUsage_GD_Mouse, - kHIDUsage_GD_Keyboard, - kHIDUsage_GD_MultiAxisController, 0}; - int *usage = usages; - - // This giant mess is necessary to create an array of match dictionaries - // that will match the devices we're interested in. - CFMutableArrayRef match = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); - nassertv(match); - while (*usage) { - CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFNumberRef page_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); - CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), page_ref); - CFRelease(page_ref); - CFNumberRef usage_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, usage); - CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), usage_ref); - CFRelease(usage_ref); - CFArrayAppendValue(match, dict); - CFRelease(dict); - ++usage; - } - IOHIDManagerSetDeviceMatchingMultiple(_hid_manager, match); - CFRelease(match); - - IOHIDManagerRegisterDeviceMatchingCallback(_hid_manager, on_match_device, this); - IOHIDManagerScheduleWithRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); - IOHIDManagerOpen(_hid_manager, kIOHIDOptionsTypeNone); -} -#else InputDeviceManager:: InputDeviceManager() : _lock("InputDeviceManager") { } -#endif - -/** - * Closes any resources that the device manager was using to listen for events. - */ -InputDeviceManager:: -~InputDeviceManager() { -#if defined(__APPLE__) - IOHIDManagerUnscheduleFromRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); - IOHIDManagerClose(_hid_manager, kIOHIDOptionsTypeNone); - CFRelease(_hid_manager); -#endif -} /** * Creates the global input manager. @@ -91,6 +35,8 @@ void InputDeviceManager:: make_global_ptr() { #ifdef _WIN32 _global_ptr = new WinInputDeviceManager; +#elif defined(__APPLE__) + _global_ptr = new IOKitInputDeviceManager; #elif defined(PHAVE_LINUX_INPUT_H) _global_ptr = new LinuxInputDeviceManager; #else diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 5f827cd3a9..8fae76c2d1 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -24,10 +24,6 @@ class WinRawInputDevice; #endif -#ifdef __APPLE__ -#include -#endif - /** * This class keeps track of all the devices on a system, and sends out events * when a device has been hot-plugged. @@ -35,15 +31,9 @@ class WinRawInputDevice; class EXPCL_PANDA_DEVICE InputDeviceManager { protected: InputDeviceManager(); - ~InputDeviceManager(); static void make_global_ptr(); -#ifdef PHAVE_LINUX_INPUT_H - InputDevice *consider_add_evdev_device(int index); - InputDevice *consider_add_js_device(int index); -#endif - PUBLISHED: InputDeviceSet get_devices() const; InputDeviceSet get_devices(InputDevice::DeviceClass device_class) const; @@ -65,12 +55,6 @@ protected: InputDeviceSet _inactive_devices; #endif -#if defined(__APPLE__) && !defined(CPPPARSER) - IOHIDManagerRef _hid_manager; - - static void on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device); -#endif - InputDeviceSet _connected_devices; static InputDeviceManager *_global_ptr; diff --git a/panda/src/device/ioKitInputDeviceManager.cxx b/panda/src/device/ioKitInputDeviceManager.cxx new file mode 100644 index 0000000000..6874d9baba --- /dev/null +++ b/panda/src/device/ioKitInputDeviceManager.cxx @@ -0,0 +1,93 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ioKitInputDeviceManager.cxx + * @author rdb + * @date 2018-02-04 + */ + +#include "ioKitInputDeviceManager.h" +#include "ioKitInputDevice.h" + +#if defined(__APPLE__) && !defined(CPPPARSER) + +/** + * Initializes the input device manager by scanning which devices are currently + * connected and setting up any platform-dependent structures necessary for + * listening for future device connect events. + */ +IOKitInputDeviceManager:: +IOKitInputDeviceManager() { + _hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (!_hid_manager) { + device_cat.error() + << "Failed to create an IOHIDManager.\n"; + return; + } + + // The types of devices we're interested in. + int page = kHIDPage_GenericDesktop; + int usages[] = {kHIDUsage_GD_GamePad, + kHIDUsage_GD_Joystick, + kHIDUsage_GD_Mouse, + kHIDUsage_GD_Keyboard, + kHIDUsage_GD_MultiAxisController, 0}; + int *usage = usages; + + // This giant mess is necessary to create an array of match dictionaries + // that will match the devices we're interested in. + CFMutableArrayRef match = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + nassertv(match); + while (*usage) { + CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFNumberRef page_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), page_ref); + CFRelease(page_ref); + CFNumberRef usage_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, usage); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), usage_ref); + CFRelease(usage_ref); + CFArrayAppendValue(match, dict); + CFRelease(dict); + ++usage; + } + IOHIDManagerSetDeviceMatchingMultiple(_hid_manager, match); + CFRelease(match); + + IOHIDManagerRegisterDeviceMatchingCallback(_hid_manager, on_match_device, this); + IOHIDManagerScheduleWithRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); + IOHIDManagerOpen(_hid_manager, kIOHIDOptionsTypeNone); +} + +/** + * Closes any resources that the device manager was using to listen for events. + */ +IOKitInputDeviceManager:: +~IOKitInputDeviceManager() { + IOHIDManagerUnscheduleFromRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes); + IOHIDManagerClose(_hid_manager, kIOHIDOptionsTypeNone); + CFRelease(_hid_manager); +} + +/** + * Called by IOKit when an input device matching our filters has been found. + */ +void IOKitInputDeviceManager:: +on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) { + InputDeviceManager *mgr = (InputDeviceManager *)ctx; + nassertv(mgr != nullptr); + nassertv(device); + + PT(InputDevice) input_device = new IOKitInputDevice(device); + if (device_cat.is_debug()) { + device_cat.debug() + << "Discovered input device " << *input_device << "\n"; + } + mgr->add_device(input_device); +} + +#endif diff --git a/panda/src/device/ioKitInputDeviceManager.h b/panda/src/device/ioKitInputDeviceManager.h new file mode 100644 index 0000000000..788413e1b0 --- /dev/null +++ b/panda/src/device/ioKitInputDeviceManager.h @@ -0,0 +1,40 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file ioKitInputDeviceManager.h + * @author rdb + * @date 2018-02-04 + */ + +#ifndef IOKITINPUTDEVICEMANAGER_H +#define IOKITINPUTDEVICEMANAGER_H + +#include "inputDeviceManager.h" + +#if defined(__APPLE__) && !defined(CPPPARSER) +#include + +/** + * The macOS implementation of InputDeviceManager. + */ +class EXPCL_PANDA_DEVICE IOKitInputDeviceManager FINAL : public InputDeviceManager { +protected: + IOKitInputDeviceManager(); + ~IOKitInputDeviceManager(); + +protected: + IOHIDManagerRef _hid_manager; + + static void on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device); + + friend class InputDeviceManager; +}; + +#endif + +#endif diff --git a/panda/src/device/p3device_composite1.cxx b/panda/src/device/p3device_composite1.cxx index 0067579474..6e7da5b78b 100644 --- a/panda/src/device/p3device_composite1.cxx +++ b/panda/src/device/p3device_composite1.cxx @@ -11,6 +11,7 @@ #include "inputDeviceNode.cxx" #include "inputDeviceSet.cxx" #include "ioKitInputDevice.cxx" +#include "ioKitInputDeviceManager.cxx" #include "linuxInputDeviceManager.cxx" #include "linuxJoystickDevice.cxx" #include "winInputDeviceManager.cxx" From c1fd2e46ded0f7a00da0d13d9658bd78b73d8321 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 4 Feb 2018 11:01:06 +0100 Subject: [PATCH 203/424] input: more device support on macOS (incl. SpaceNavigator) --- panda/src/device/ioKitInputDevice.cxx | 104 +++++++++++++++++++------- 1 file changed, 77 insertions(+), 27 deletions(-) diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index 3b18b3546c..85269d49e6 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -46,8 +46,15 @@ IOKitInputDevice(IOHIDDeviceRef device) : CFStringRef name = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); if (name) { + buffer[0] = 0; CFStringGetCString(name, buffer, sizeof(buffer), kCFStringEncodingUTF8); - _name = buffer; + + // Strip trailing spaces. + size_t len = strlen(buffer); + while (isspace(buffer[len - 1])) { + --len; + } + _name.assign(buffer, len); } CFStringRef mfg = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey)); @@ -89,6 +96,18 @@ IOKitInputDevice(IOHIDDeviceRef device) : } else if (_vendor_id == 0x044f && _product_id == 0xb108) { // T.Flight Hotas X _device_class = DC_flight_stick; + } else if (_vendor_id == 0x046d && + (_product_id == 0xc623 || + _product_id == 0xc625 || + _product_id == 0xc626 || + _product_id == 0xc627 || + _product_id == 0xc628 || + _product_id == 0xc629 || + _product_id == 0xc62b)) { + // 3Dconnexion SpaceNavigator and friends. + _device_class = DC_3d_mouse; + } else if (_name == "usb gamepad") { + _device_class = DC_gamepad; } CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, 0); @@ -189,15 +208,25 @@ parse_element(IOHIDElementRef element) { case kHIDUsage_GD_Z: if (_device_class == DC_gamepad) { axis = C_left_trigger; - } else { + } else if (_device_class == DC_flight_stick) { axis = C_throttle; + } else { + axis = C_z; } break; case kHIDUsage_GD_Rx: - axis = C_right_x; + if (_device_class == DC_gamepad) { + axis = C_right_x; + } else { + axis = C_pitch; + } break; case kHIDUsage_GD_Ry: - axis = C_right_y; + if (_device_class == DC_gamepad) { + axis = C_right_y; + } else { + axis = C_roll; + } break; case kHIDUsage_GD_Rz: if (_device_class == DC_gamepad) { @@ -259,7 +288,8 @@ parse_element(IOHIDElementRef element) { if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. add_control(axis, max, min, true); - } else if (axis == C_yaw || axis == C_left_y || axis == C_right_y) { + } else if (axis == C_yaw || axis == C_rudder || axis == C_left_y || axis == C_right_y || + (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z || axis == C_roll))) { // We'd like to reverse the Y axis to match the XInput behavior. // We also reverse yaw to obey the right-hand rule. add_control(axis, max, min); @@ -598,28 +628,48 @@ parse_element(IOHIDElementRef element) { case kHIDPage_Button: if (_device_class == DC_gamepad) { - // These seem to be the button mappings exposed by the 360Controller - // driver I don't know if other drivers do the same thing at all. - static const ButtonHandle gamepad_buttons[] = { - ButtonHandle::none(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_x(), - GamepadButton::action_y(), - GamepadButton::lshoulder(), - GamepadButton::rshoulder(), - GamepadButton::lstick(), - GamepadButton::rstick(), - GamepadButton::start(), - GamepadButton::back(), - GamepadButton::guide(), - GamepadButton::dpad_up(), - GamepadButton::dpad_down(), - GamepadButton::dpad_left(), - GamepadButton::dpad_right(), - }; - if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { - handle = gamepad_buttons[usage]; + if (_vendor_id == 0x0810 && _product_id == 0xe501) { + // SNES-style USB gamepad + static const ButtonHandle gamepad_buttons[] = { + ButtonHandle::none(), + GamepadButton::action_x(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + ButtonHandle::none(), + ButtonHandle::none(), + GamepadButton::back(), + GamepadButton::start(), + }; + if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + handle = gamepad_buttons[usage]; + } + } else { + // These seem to be the button mappings exposed by the 360Controller + // driver. I don't know if other drivers do the same thing at all. + static const ButtonHandle gamepad_buttons[] = { + ButtonHandle::none(), + GamepadButton::action_a(), + GamepadButton::action_b(), + GamepadButton::action_x(), + GamepadButton::action_y(), + GamepadButton::lshoulder(), + GamepadButton::rshoulder(), + GamepadButton::lstick(), + GamepadButton::rstick(), + GamepadButton::start(), + GamepadButton::back(), + GamepadButton::guide(), + GamepadButton::dpad_up(), + GamepadButton::dpad_down(), + GamepadButton::dpad_left(), + GamepadButton::dpad_right(), + }; + if (usage < sizeof(gamepad_buttons) / sizeof(ButtonHandle)) { + handle = gamepad_buttons[usage]; + } } } else if (_device_class == DC_flight_stick) { if (usage > 0) { From 73fd10281b990cfc16cdfeab73a76a6926def767 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 3 Feb 2018 14:00:16 -0800 Subject: [PATCH 204/424] deploy-ng: Add build directory to exclude patterns --- direct/src/showutil/dist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 94375c4a8a..9b545c82f8 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -422,6 +422,7 @@ class build_apps(distutils.core.Command): '__pycache__', '*.pyc', '*.py', + '{}/*'.format(self.build_base), ] ignore_copy_list += self.exclude_patterns ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] From 8f919b7e405e0a880f5af5ee36a3447de552d813 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 12 Feb 2018 17:47:55 -0800 Subject: [PATCH 205/424] deploy-ng: Fix to handle utf-8 Python source files on all systems NOTE: This only works for Python 3 --- direct/src/showutil/FreezeTool.py | 58 ++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index e55468c2c6..891b6b2108 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -8,7 +8,7 @@ import marshal import imp import platform import struct -from io import StringIO, BytesIO, TextIOWrapper +import io import distutils.sysconfig as sysconf import zipfile @@ -1143,11 +1143,14 @@ class Freezer: stuff = ("", "rb", imp.PY_COMPILED) self.mf.load_module(mdef.moduleName, fp, pathname, stuff) else: - if mdef.text: - fp = StringIO(mdef.text) - else: - fp = open(pathname, 'U') stuff = ("", "r", imp.PY_SOURCE) + if mdef.text: + fp = io.StringIO(mdef.text) + elif sys.version_info >= (3, 0): + fp = open(pathname, 'rb') + stuff = ("", "rb", imp.PY_SOURCE) + else: + fp = open(pathname, 'r') self.mf.load_module(mdef.moduleName, fp, pathname, stuff) if tempPath: @@ -1910,7 +1913,7 @@ class Freezer: if data.startswith(b'MZ'): # A Windows PE file. pe = pefile.PEFile() - pe.read(BytesIO(data)) + pe.read(io.BytesIO(data)) addr = pe.get_export_address(symbol_name) if addr is not None: # We found it, return its offset in the file. @@ -2162,8 +2165,8 @@ class PandaModuleFinder(modulefinder.ModuleFinder): Returns None if the module could not be found. """ if os.path.isfile(path): - if sys.version_info >= (3, 0) and 'b' not in mode: - return open(path, mode, encoding='utf8') + if 'b' not in mode: + return io.open(path, mode, encoding='utf8') else: return open(path, mode) @@ -2189,7 +2192,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): return None if sys.version_info >= (3, 0) and 'b' not in mode: - return TextIOWrapper(fp, encoding='utf8') + return io.TextIOWrapper(fp, encoding='utf8') return fp # Look at the parent directory. @@ -2198,6 +2201,43 @@ class PandaModuleFinder(modulefinder.ModuleFinder): return None + def load_module(self, fqname, fp, pathname, file_info): + """Copied from ModuleFinder.load_module with fixes to handle sending bytes + to compile() for PY_SOURCE types. Sending bytes to compile allows it to + handle file encodings.""" + + if sys.version_info < (3, 0): + # fallback to original version for Python 2 + return super.load_module(fqname, fp, pathname, file_info) + + suffix, mode, type = file_info + self.msgin(2, "load_module", fqname, fp and "fp", pathname) + if type == imp.PKG_DIRECTORY: + m = self.load_package(fqname, pathname) + self.msgout(2, "load_module ->", m) + return m + if type == imp.PY_SOURCE: + newline = b'\n' if 'b' in mode else '\n' + co = compile(fp.read()+newline, pathname, 'exec') + elif type == imp.PY_COMPILED: + try: + marshal_data = importlib._bootstrap_external._validate_bytecode_header(fp.read()) + except ImportError as exc: + self.msgout(2, "raise ImportError: " + str(exc), pathname) + raise + co = marshal.loads(marshal_data) + else: + co = None + m = self.add_module(fqname) + m.__file__ = pathname + if co: + if self.replace_paths: + co = self.replace_paths_in_code(co) + m.__code__ = co + self.scan_code(co, m) + self.msgout(2, "load_module ->", m) + return m + def find_module(self, name, path=None, parent=None): """ Finds a module with the indicated name on the given search path (or self.path if None). Returns a tuple like (fp, path, stuff), where From 0c3c6c36bfd8dbf80e70a1f3276b276d7575cd83 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 12 Feb 2018 22:19:41 -0800 Subject: [PATCH 206/424] deploy-ng: Fix loading submodules for hidden imports Fixes #242 --- direct/src/showutil/FreezeTool.py | 116 +++++++++++++++++------------- tests/setup.py | 9 --- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 891b6b2108..d506dd56d4 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -71,11 +71,6 @@ hiddenImports = { 'pytest': pytest_imports(), 'pkg_resources': [ 'pkg_resources.*.*', - # TODO why does the above not get these modules too? - 'pkg_resources._vendor.packaging.*', - 'pkg_resources._vendor.packaging.version', - 'pkg_resources._vendor.packaging.specifiers', - 'pkg_resources._vendor.packaging.requirements', ], 'xml.etree.cElementTree': ['xml.etree.ElementTree'], 'datetime': ['_strptime'], @@ -907,6 +902,60 @@ class Freezer: return modules + def _gatherSubmodules(self, moduleName, implicit = False, newName = None, + filename = None, guess = False, fromSource = None, + text = None): + if not newName: + newName = moduleName + + assert(moduleName.endswith('.*')) + assert(newName.endswith('.*')) + + mdefs = {} + + # Find the parent module, so we can get its directory. + parentName = moduleName[:-2] + newParentName = newName[:-2] + parentNames = [(parentName, newParentName)] + + if parentName.endswith('.*'): + assert(newParentName.endswith('.*')) + # Another special case. The parent name "*" means to + # return all possible directories within a particular + # directory. + + topName = parentName[:-2] + newTopName = newParentName[:-2] + parentNames = [] + modulePath = self.getModulePath(topName) + if modulePath: + for dirname in modulePath: + for basename in os.listdir(dirname): + if os.path.exists(os.path.join(dirname, basename, '__init__.py')): + parentName = '%s.%s' % (topName, basename) + newParentName = '%s.%s' % (newTopName, basename) + if self.getModulePath(parentName): + parentNames.append((parentName, newParentName)) + + for parentName, newParentName in parentNames: + modules = self.getModuleStar(parentName) + + if modules == None: + # It's actually a regular module. + mdef[newParentName] = self.ModuleDef( + parentName, implicit = implicit, guess = guess, + fromSource = fromSource, text = text) + + else: + # Now get all the py files in the parent directory. + for basename in modules: + moduleName = '%s.%s' % (parentName, basename) + newName = '%s.%s' % (newParentName, basename) + mdefs[newName] = self.ModuleDef( + moduleName, implicit = implicit, guess = True, + fromSource = fromSource) + return mdefs + def addModule(self, moduleName, implicit = False, newName = None, filename = None, guess = False, fromSource = None, text = None): @@ -933,49 +982,9 @@ class Freezer: newName = moduleName if moduleName.endswith('.*'): - assert(newName.endswith('.*')) - # Find the parent module, so we can get its directory. - parentName = moduleName[:-2] - newParentName = newName[:-2] - parentNames = [(parentName, newParentName)] - - if parentName.endswith('.*'): - assert(newParentName.endswith('.*')) - # Another special case. The parent name "*" means to - # return all possible directories within a particular - # directory. - - topName = parentName[:-2] - newTopName = newParentName[:-2] - parentNames = [] - modulePath = self.getModulePath(topName) - if modulePath: - for dirname in modulePath: - for basename in os.listdir(dirname): - if os.path.exists(os.path.join(dirname, basename, '__init__.py')): - parentName = '%s.%s' % (topName, basename) - newParentName = '%s.%s' % (newTopName, basename) - if self.getModulePath(parentName): - parentNames.append((parentName, newParentName)) - - for parentName, newParentName in parentNames: - modules = self.getModuleStar(parentName) - - if modules == None: - # It's actually a regular module. - self.modules[newParentName] = self.ModuleDef( - parentName, implicit = implicit, guess = guess, - fromSource = fromSource, text = text) - - else: - # Now get all the py files in the parent directory. - for basename in modules: - moduleName = '%s.%s' % (parentName, basename) - newName = '%s.%s' % (newParentName, basename) - mdef = self.ModuleDef( - moduleName, implicit = implicit, guess = True, - fromSource = fromSource) - self.modules[newName] = mdef + self.modules.update(self._gatherSubmodules( + moduleName, implicit, newName, filename, + guess, fromSource, text)) else: # A normal, explicit module name. self.modules[newName] = self.ModuleDef( @@ -1069,7 +1078,16 @@ class Freezer: for origName in list(self.mf.modules.keys()): hidden = hiddenImports.get(origName, []) for modname in hidden: - self.__loadModule(self.ModuleDef(modname, implicit = True)) + print(origName, modname) + if modname.endswith('.*'): + mdefs = self._gatherSubmodules(modname, implicit = True) + for mdef in mdefs.values(): + try: + self.__loadModule(mdef) + except ImportError: + pass + else: + self.__loadModule(self.ModuleDef(modname, implicit = True)) # Now, any new modules we found get added to the export list. for origName in list(self.mf.modules.keys()): diff --git a/tests/setup.py b/tests/setup.py index 823044a1d4..a8ce20715e 100644 --- a/tests/setup.py +++ b/tests/setup.py @@ -12,15 +12,6 @@ setup( 'pandagl', 'p3openal_audio', ], - 'include_modules': { - '*': [ - 'pkg_resources.*.*', - # TODO why does the above not get these modules too? - 'pkg_resources._vendor.packaging.*', - 'pkg_resources._vendor.packaging.version', - 'pkg_resources._vendor.packaging.specifiers', - 'pkg_resources._vendor.packaging.requirements', - ] + pytest.freeze_includes(), }, 'platforms': [ 'manylinux1_x86_64', From 846dc9c97b3ca71844e1d68328d6d7c6ab712701 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 12 Feb 2018 22:21:44 -0800 Subject: [PATCH 207/424] deploy-ng: Add hidden imports for keyring.backends --- direct/src/showutil/FreezeTool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index d506dd56d4..d22453114a 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -74,6 +74,7 @@ hiddenImports = { ], 'xml.etree.cElementTree': ['xml.etree.ElementTree'], 'datetime': ['_strptime'], + 'keyring.backends': ['keyring.backends.*'], } # These are missing modules that we've reported already this session. From 15dd70f25442a380900d2b418f95365433b23d14 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 13 Feb 2018 11:23:06 +0100 Subject: [PATCH 208/424] deploy-ng: fix regression in Python 2 introduced by 8f919b7 --- direct/src/showutil/FreezeTool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index d22453114a..c5a19080cc 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -2227,7 +2227,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): if sys.version_info < (3, 0): # fallback to original version for Python 2 - return super.load_module(fqname, fp, pathname, file_info) + return modulefinder.ModuleFinder.load_module(self, fqname, fp, pathname, file_info) suffix, mode, type = file_info self.msgin(2, "load_module", fqname, fp and "fp", pathname) From 6f17bc9224b4ffc8bc094f1e0ce034e73c06d9c0 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 24 Feb 2018 11:04:01 -0800 Subject: [PATCH 209/424] deploy-ng: Remove debug print --- direct/src/showutil/FreezeTool.py | 1 - 1 file changed, 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index c5a19080cc..11b4fd3ce0 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1079,7 +1079,6 @@ class Freezer: for origName in list(self.mf.modules.keys()): hidden = hiddenImports.get(origName, []) for modname in hidden: - print(origName, modname) if modname.endswith('.*'): mdefs = self._gatherSubmodules(modname, implicit = True) for mdef in mdefs.values(): From 5b4041bf234dc3f9b2b2cc799cceb088f3668881 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 24 Feb 2018 11:04:40 -0800 Subject: [PATCH 210/424] deploy-ng: Fix handling of unicode Python files for Python 2 --- direct/src/showutil/FreezeTool.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 11b4fd3ce0..789d1cdad5 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -752,7 +752,7 @@ class Freezer: if self.platform == PandaSystem.getPlatform(): self.moduleSuffixes = imp.get_suffixes() else: - self.moduleSuffixes = [('.py', 'r', 1), ('.pyc', 'rb', 2)] + self.moduleSuffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)] if 'linux' in self.platform: self.moduleSuffixes += [ ('.cpython-{0}{1}m-x86_64-linux-gnu.so'.format(*sys.version_info), 'rb', 3), @@ -1161,14 +1161,11 @@ class Freezer: stuff = ("", "rb", imp.PY_COMPILED) self.mf.load_module(mdef.moduleName, fp, pathname, stuff) else: - stuff = ("", "r", imp.PY_SOURCE) + stuff = ("", "rb", imp.PY_SOURCE) if mdef.text: fp = io.StringIO(mdef.text) - elif sys.version_info >= (3, 0): - fp = open(pathname, 'rb') - stuff = ("", "rb", imp.PY_SOURCE) else: - fp = open(pathname, 'r') + fp = open(pathname, 'rb') self.mf.load_module(mdef.moduleName, fp, pathname, stuff) if tempPath: @@ -2224,10 +2221,6 @@ class PandaModuleFinder(modulefinder.ModuleFinder): to compile() for PY_SOURCE types. Sending bytes to compile allows it to handle file encodings.""" - if sys.version_info < (3, 0): - # fallback to original version for Python 2 - return modulefinder.ModuleFinder.load_module(self, fqname, fp, pathname, file_info) - suffix, mode, type = file_info self.msgin(2, "load_module", fqname, fp and "fp", pathname) if type == imp.PKG_DIRECTORY: From 94b5fa1e90c6746b587bcf1796a16b6df61886c1 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 24 Feb 2018 21:37:23 +0100 Subject: [PATCH 211/424] deploy-ng: fix inclusion of additional python DLLs (eg. sqlite, tk) Fixes: #239 --- direct/src/showutil/dist.py | 23 ++++++++++++++++------- makepanda/makewheel.py | 3 +++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 9b545c82f8..831cca8d5d 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -10,6 +10,7 @@ import zipfile import shutil import struct import io +import imp import distutils.core import distutils.log @@ -272,6 +273,7 @@ class build_apps(distutils.core.Command): # Create runtimes freezer_extras = set() freezer_modules = set() + ext_suffixes = set() def create_runtime(appname, mainscript, use_console): freezer = FreezeTool.Freezer(platform=platform, path=path) freezer.addModule('__main__', filename=mainscript) @@ -320,6 +322,9 @@ class build_apps(distutils.core.Command): freezer_extras.update(freezer.extras) freezer_modules.update(freezer.getAllModuleNames()) + for suffix in freezer.moduleSuffixes: + if suffix[2] == imp.C_EXTENSION: + ext_suffixes.add(suffix[0]) for appname, scriptname in self.gui_apps.items(): create_runtime(appname, scriptname, False) @@ -332,14 +337,18 @@ class build_apps(distutils.core.Command): whl_modules_ext = '' if use_wheels: # Get the module libs - whl_modules = [ - i.replace('deploy_libs/', '') for i in p3dwhl.namelist() if i.startswith('deploy_libs/') - ] + whl_modules = [] - # Pull off extension - if whl_modules: - whl_modules_ext = '.'.join(whl_modules[0].split('.')[1:]) - whl_modules = [i.split('.')[0] for i in whl_modules] + for i in p3dwhl.namelist(): + if not i.startswith('deploy_libs/'): + continue + + if not any(i.endswith(suffix) for suffix in ext_suffixes): + continue + + base = os.path.basename(i) + whl_modules.append(base) + whl_modules_ext = base.partition('.')[2] # Make sure to copy any builtins that have shared objects in the # deploy libs, assuming they are not already in freezer_extras. diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 5cf851c1ee..e70dbaa862 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -506,6 +506,9 @@ def makewheel(version, output_dir, platform=None): whl = WheelFile('panda3d', version, platform) whl.lib_path = [libs_dir] + if sys.platform == "win32": + whl.lib_path.append(join(output_dir, "python", "DLLs")) + # Add the trees with Python modules. whl.write_directory('direct', direct_dir) From c6f49790695f56a0eb6405a4001c86dc477633ab Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 24 Feb 2018 21:38:39 +0100 Subject: [PATCH 212/424] deploy-ng: fix unicode modules with encoding on Python 2 --- direct/src/showutil/FreezeTool.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 789d1cdad5..c218bc8e41 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -751,6 +751,11 @@ class Freezer: # Suffix/extension for Python C extension modules if self.platform == PandaSystem.getPlatform(): self.moduleSuffixes = imp.get_suffixes() + + # Set extension for Python files to binary mode + for i, suffix in enumerate(self.moduleSuffixes): + if suffix[2] == imp.PY_SOURCE: + self.moduleSuffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE) else: self.moduleSuffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)] if 'linux' in self.platform: From 1afcda4700486e242f3aa4b8fe04775d0eb2f00d Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 27 Feb 2018 18:30:22 -0800 Subject: [PATCH 213/424] deploy-ng: Allow specifying installers for each platform --- direct/src/showutil/dist.py | 77 ++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 831cca8d5d..9dd592b522 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -24,6 +24,28 @@ if 'basestring' not in globals(): basestring = str +def _parse_list(input): + if isinstance(input, basestring): + input = input.strip().replace(',', '\n') + if input: + return [item.strip() for item in input.split('\n') if item.strip()] + else: + return [] + else: + return input + + +def _parse_dict(input): + if isinstance(input, dict): + return input + d = {} + for item in _parse_list(input): + key, sep, value = item.partition('=') + d[key.strip()] = value.strip() + return d + + + def egg2bam(_build_cmd, srcpath, dstpath): dstpath = dstpath + '.bam' subprocess.call([ @@ -96,25 +118,6 @@ class build_apps(distutils.core.Command): def finalize_options(self): # We need to massage the inputs a bit in case they came from a # setup.cfg file. - def _parse_list(input): - if isinstance(input, basestring): - input = input.strip().replace(',', '\n') - if input: - return [item.strip() for item in input.split('\n') if item.strip()] - else: - return [] - else: - return input - - def _parse_dict(input): - if isinstance(input, dict): - return input - d = {} - for item in _parse_list(input): - key, sep, value = item.partition('=') - d[key.strip()] = value.strip() - return d - self.gui_apps = _parse_dict(self.gui_apps) self.console_apps = _parse_dict(self.console_apps) @@ -758,14 +761,22 @@ class build_apps(distutils.core.Command): class bdist_apps(distutils.core.Command): + DEFAULT_INSTALLERS = { + 'manylinux1_x86_64': ['gztar'], + 'manylinux1_i386': ['gztar'], + # Everything else defaults to ['zip'] + } + description = 'bundle built Panda3D applications into distributable forms' user_options = [] def initialize_options(self): - pass + self.installers = {} def finalize_options(self): - pass + # We need to massage the inputs a bit in case they came from a + # setup.cfg file. + self.installers = _parse_dict(self.installers) def run(self): build_cmd = self.get_finalized_command('build_apps') @@ -780,20 +791,26 @@ class bdist_apps(distutils.core.Command): for platform in platforms: build_dir = os.path.join(build_base, platform) - base_dir = self.distribution.get_name() - temp_dir = os.path.join(build_base, base_dir) - archive_format = 'gztar' if 'linux' in platform else 'zip' basename = '{}_{}'.format(self.distribution.get_fullname(), platform) + installers = self.installers.get(platform, self.DEFAULT_INSTALLERS.get(platform, ['zip'])) + print(installers) - if (os.path.exists(temp_dir)): - shutil.rmtree(temp_dir) - shutil.copytree(build_dir, temp_dir) + for installer in installers: + self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO) - self.announce('Building {} for platform: {}'.format(archive_format, platform), distutils.log.INFO) + if installer in ('zip', 'gztar'): + base_dir = self.distribution.get_name() + temp_dir = os.path.join(build_base, base_dir) + if (os.path.exists(temp_dir)): + shutil.rmtree(temp_dir) + shutil.copytree(build_dir, temp_dir) - distutils.archive_util.make_archive(basename, archive_format, root_dir=build_base, base_dir=base_dir) + distutils.archive_util.make_archive(basename, installer, root_dir=build_base, base_dir=base_dir) + + shutil.rmtree(temp_dir) + else: + self.announce('\tUnknown installer: {}'.format(installer), distutils.log.ERROR) - shutil.rmtree(temp_dir) def setup(**attrs): commandClasses = attrs.setdefault("cmdclass", {}) From fcacc3c2be059cf98b79da16bfcd1ba67013ba34 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 27 Feb 2018 18:37:00 -0800 Subject: [PATCH 214/424] deploy-ng: librt.so.1 to libutil.so.1 to exclude_dependencies --- direct/src/showutil/dist.py | 2 +- samples/asteroids/setup.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 9dd592b522..95a0ccc640 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -101,7 +101,7 @@ class build_apps(distutils.core.Command): 'libdl.so.2', 'libstdc++.so.6', 'libm.so.6', 'libgcc_s.so.1', 'libpthread.so.0', 'libc.so.6', 'ld-linux-x86-64.so.2', 'libgl.so.1', 'libx11.so.6', 'libreadline.so.5', 'libncursesw.so.5', - 'libbz2.so.1', 'libz.so.1', 'liblzma.so.0', + 'libbz2.so.1', 'libz.so.1', 'liblzma.so.0', 'librt.so.1', 'libutil.so.1', ] # We keep track of the zip files we've opened. diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index c592c0911f..8fb122585b 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -18,9 +18,9 @@ setup( ], 'platforms': [ 'manylinux1_x86_64', - 'macosx_10_6_x86_64', - 'win32', - 'win_amd64', + #'macosx_10_6_x86_64', + #'win32', + #'win_amd64', ], } } From c8da53a81f9706ba975a5e15dedb9ad5c55c7003 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 1 Mar 2018 22:02:58 +0100 Subject: [PATCH 215/424] deploy-ng: fix regression with builtin modules not being added This happens only when the system Python has the module built-in (eg. on Ubuntu where most modules are builtin) but deploy-ng doesn't. Regression was introduced by 94b5fa1e90c6746b587bcf1796a16b6df61886c1 Fixes #264 --- direct/src/showutil/dist.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 95a0ccc640..24a953afd2 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -350,8 +350,9 @@ class build_apps(distutils.core.Command): continue base = os.path.basename(i) - whl_modules.append(base) - whl_modules_ext = base.partition('.')[2] + module, _, ext = base.partition('.') + whl_modules.append(module) + whl_modules_ext = ext # Make sure to copy any builtins that have shared objects in the # deploy libs, assuming they are not already in freezer_extras. From 7a7eca25ee36a8ac5c8f282c27b03ad50ee426e8 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 12 Mar 2018 21:29:39 -0700 Subject: [PATCH 216/424] deploy-ng: Only add Python files that were frozen to the ignore list This allows copying over unfrozen Python files by adding modules to the exclude_modules dictionary (behaves like include_modules) and then making sure the Python file matches a pattern in the include_patterns list. --- direct/src/showutil/dist.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 24a953afd2..ec767ecf68 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -124,8 +124,8 @@ class build_apps(distutils.core.Command): self.rename_paths = _parse_dict(self.rename_paths) self.include_patterns = _parse_list(self.include_patterns) self.exclude_patterns = _parse_list(self.exclude_patterns) - self.include_modules = _parse_list(self.include_modules) - self.exclude_modules = _parse_list(self.exclude_modules) + self.include_modules = _parse_dict(self.include_modules) + self.exclude_modules = _parse_dict(self.exclude_modules) self.plugins = _parse_list(self.plugins) num_gui_apps = len(self.gui_apps) @@ -276,6 +276,7 @@ class build_apps(distutils.core.Command): # Create runtimes freezer_extras = set() freezer_modules = set() + freezer_modpaths = set() ext_suffixes = set() def create_runtime(appname, mainscript, use_console): freezer = FreezeTool.Freezer(platform=platform, path=path) @@ -325,6 +326,10 @@ class build_apps(distutils.core.Command): freezer_extras.update(freezer.extras) freezer_modules.update(freezer.getAllModuleNames()) + freezer_modpaths.update({ + mod[1].filename.to_os_specific() + for mod in freezer.getModuleDefs() if mod[1].filename + }) for suffix in freezer.moduleSuffixes: if suffix[2] == imp.C_EXTENSION: ext_suffixes.add(suffix[0]) @@ -434,10 +439,10 @@ class build_apps(distutils.core.Command): ignore_copy_list = [ '__pycache__', '*.pyc', - '*.py', '{}/*'.format(self.build_base), ] ignore_copy_list += self.exclude_patterns + ignore_copy_list += freezer_modpaths ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] include_copy_list = [p3d.GlobPattern(i) for i in self.include_patterns] From 4d47603e8792812f83dd057d0c2468d644b09b40 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 18 Mar 2018 16:49:34 -0700 Subject: [PATCH 217/424] deploy-ng: Allow embedding PRC data Also parse PRC data and remove lines for plugins that are not included in the build. --- direct/src/showutil/dist.py | 73 ++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index ec767ecf68..132cf85acc 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -82,6 +82,7 @@ class build_apps(distutils.core.Command): self.exclude_modules = {} self.platforms = [] self.plugins = [] + self.embed_prc_data = True self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] self.file_handlers= { @@ -273,6 +274,51 @@ class build_apps(distutils.core.Command): self.announce('Building runtime for platform: {}'.format(platform), distutils.log.INFO) + # Gather PRC data + prcstring = '' + if not use_wheels: + dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) + libdir = os.path.dirname(dtool_fn.to_os_specific()) + etcdir = os.path.join(libdir, '..', 'etc') + + for fn in os.listdir(etcdir): + if fn.lower().endswith('.prc'): + with open(os.path.join(etcdir, fn)) as f: + prcstring += f.read() + else: + etcfiles = [i for i in p3dwhl.namelist() if i.endswith('.prc')] + for fn in etcfiles: + with p3dwhl.open(fn) as f: + prcstring += f.read().decode('utf8') + + # Clenup PRC data + prcexport = [] + check_plugins = [ + 'pandaegg', + 'p3ffmpeg', + 'p3ptloader', + ] + for ln in prcstring.split('\n'): + useline = True + if ln.startswith('#') or not ln: + useline = False + else: + for plugin in check_plugins: + if plugin in ln and plugin not in self.plugins: + useline = False + break + + if useline: + prcexport.append(ln) + + # Export PRC data + prcexport = '\n'.join(prcexport) + if not self.embed_prc_data: + etcdir = os.path.join(builddir, 'etc') + os.makedirs(etcdir) + with open (os.path.join(etcdir, '00-panda3d.prc'), 'w') as f: + f.write(prcexport) + # Create runtimes freezer_extras = set() freezer_modules = set() @@ -306,7 +352,7 @@ class build_apps(distutils.core.Command): stub_file = open(stub_path, 'rb') freezer.generateRuntimeFromStub(target_path, stub_file, use_console, { - 'prc_data': None, + 'prc_data': prcexport if self.embed_prc_data else None, 'default_prc_dir': None, 'prc_dir_envvars': None, 'prc_path_envvars': None, @@ -409,31 +455,6 @@ class build_apps(distutils.core.Command): target_path = os.path.join(builddir, basename) self.copy_with_dependencies(source_path, target_path, search_path) - # Copy Panda3D files - etcdir = os.path.join(builddir, 'etc') - if not use_wheels: - # etc - dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()) - libdir = os.path.dirname(dtool_fn.to_os_specific()) - src = os.path.join(libdir, '..', 'etc') - shutil.copytree(src, etcdir) - else: - os.makedirs(etcdir) - - # Combine prc files with libs and copy the whole list - panda_files = [i for i in p3dwhl.namelist() if i.endswith('.prc')] - for pf in panda_files: - dstdir = etcdir if pf.endswith('.prc') else builddir - target_path = os.path.join(dstdir, os.path.basename(pf)) - source_path = os.path.join(p3dwhlfn, pf) - - # If this is a dynamic library, search for dependencies. - search_path = [os.path.dirname(source_path)] - if use_wheels: - search_path.append(os.path.join(p3dwhlfn, 'deploy_libs')) - - self.copy_with_dependencies(source_path, target_path, search_path) - # Copy Game Files self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO) ignore_copy_list = [ From 164ef3fa4b0d0a218ddc326a051b117826634ce8 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 21 Mar 2018 18:28:42 -0700 Subject: [PATCH 218/424] deploy-ng: Add options for user-supplied PRC data --- direct/src/showutil/dist.py | 42 +++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 132cf85acc..0173b8ecd5 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -83,6 +83,8 @@ class build_apps(distutils.core.Command): self.platforms = [] self.plugins = [] self.embed_prc_data = True + self.extra_prc_files = [] + self.extra_prc_data = '' self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] self.file_handlers= { @@ -128,6 +130,7 @@ class build_apps(distutils.core.Command): self.include_modules = _parse_dict(self.include_modules) self.exclude_modules = _parse_dict(self.exclude_modules) self.plugins = _parse_list(self.plugins) + self.extra_prc_files = _parse_list(self.extra_prc_files) num_gui_apps = len(self.gui_apps) num_console_apps = len(self.console_apps) @@ -290,26 +293,38 @@ class build_apps(distutils.core.Command): for fn in etcfiles: with p3dwhl.open(fn) as f: prcstring += f.read().decode('utf8') + user_prcstring = self.extra_prc_data + for fn in self.extra_prc_files: + with open(fn) as f: + user_prcstring += f.read() # Clenup PRC data - prcexport = [] check_plugins = [ + #TODO find a better way to get this list 'pandaegg', 'p3ffmpeg', 'p3ptloader', ] - for ln in prcstring.split('\n'): - useline = True - if ln.startswith('#') or not ln: - useline = False - else: - for plugin in check_plugins: - if plugin in ln and plugin not in self.plugins: - useline = False - break - - if useline: - prcexport.append(ln) + def parse_prc(prcstr, warn_on_missing_plugin): + out = [] + for ln in prcstr.split('\n'): + ln = ln.strip() + useline = True + if ln.startswith('#') or not ln: + useline = False + else: + for plugin in check_plugins: + if plugin in ln and plugin not in self.plugins: + useline = False + if warn_on_missing_plugin: + self.warn( + "Missing plugin ({0}) referenced in user PRC data".format(plugin) + ) + break + if useline: + out.append(ln) + return out + prcexport = parse_prc(prcstring, 0) + parse_prc(user_prcstring, 1) # Export PRC data prcexport = '\n'.join(prcexport) @@ -464,6 +479,7 @@ class build_apps(distutils.core.Command): ] ignore_copy_list += self.exclude_patterns ignore_copy_list += freezer_modpaths + ignore_copy_list += self.extra_prc_files ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] include_copy_list = [p3d.GlobPattern(i) for i in self.include_patterns] From b91cb655c37caed3c2d8c03db43d1ec2767fbcdd Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 22 Mar 2018 18:34:09 -0700 Subject: [PATCH 219/424] deploy-ng: Expose default_prc_dir as a build_apps option If not set, this defaults to 'etc' if embed_prc_data is False or '' (no default PRC dir) if embed_prc_data is True. --- direct/src/showutil/dist.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 0173b8ecd5..345f4edeb8 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -85,6 +85,7 @@ class build_apps(distutils.core.Command): self.embed_prc_data = True self.extra_prc_files = [] self.extra_prc_data = '' + self.default_prc_dir = None self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] self.file_handlers= { @@ -132,6 +133,9 @@ class build_apps(distutils.core.Command): self.plugins = _parse_list(self.plugins) self.extra_prc_files = _parse_list(self.extra_prc_files) + if self.default_prc_dir is None: + self.default_prc_dir = 'etc' if not self.embed_prc_data else '' + num_gui_apps = len(self.gui_apps) num_console_apps = len(self.console_apps) @@ -329,9 +333,10 @@ class build_apps(distutils.core.Command): # Export PRC data prcexport = '\n'.join(prcexport) if not self.embed_prc_data: - etcdir = os.path.join(builddir, 'etc') - os.makedirs(etcdir) - with open (os.path.join(etcdir, '00-panda3d.prc'), 'w') as f: + prcdir = self.default_prc_dir.replace('', '') + prcdir = os.path.join(builddir, prcdir) + os.makedirs(prcdir) + with open (os.path.join(prcdir, '00-panda3d.prc'), 'w') as f: f.write(prcexport) # Create runtimes @@ -368,7 +373,7 @@ class build_apps(distutils.core.Command): freezer.generateRuntimeFromStub(target_path, stub_file, use_console, { 'prc_data': prcexport if self.embed_prc_data else None, - 'default_prc_dir': None, + 'default_prc_dir': self.default_prc_dir, 'prc_dir_envvars': None, 'prc_path_envvars': None, 'prc_patterns': None, From d613523f14782b4bdf9f5904082b493bd32337cf Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 22 Mar 2018 18:53:26 -0700 Subject: [PATCH 220/424] deploy-ng: Add tests/build_samples.py script This script will run python setup.py bdist_apps on each sample in a list (currently only contains asteroids). The script will fail if any of the builds fail. We can use this for some automated sanitity checking of deploy-ng builds. --- samples/asteroids/setup.py | 6 +++--- tests/build_samples.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 tests/build_samples.py diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 8fb122585b..c592c0911f 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -18,9 +18,9 @@ setup( ], 'platforms': [ 'manylinux1_x86_64', - #'macosx_10_6_x86_64', - #'win32', - #'win_amd64', + 'macosx_10_6_x86_64', + 'win32', + 'win_amd64', ], } } diff --git a/tests/build_samples.py b/tests/build_samples.py new file mode 100644 index 0000000000..3abd2bfb2f --- /dev/null +++ b/tests/build_samples.py @@ -0,0 +1,19 @@ +import os +import subprocess + +SAMPLES_TO_BUILD = [ + 'asteroids', +] +SAMPLES_DIR = os.path.join(os.path.dirname(__file__), '..', 'samples') + +def main(): + for sample in SAMPLES_TO_BUILD: + sampledir = os.path.join(SAMPLES_DIR, sample) + os.chdir(sampledir) + + # This will raise a CalledProcessError if the build fails, which will cause + # this script to fail + subprocess.check_call(['python', 'setup.py', 'bdist_apps']) + +if __name__ == '__main__': + main() From 01ae235bfb4cf1bca092a668e05cd9bfb3fae6b2 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Fri, 30 Mar 2018 13:42:35 -0700 Subject: [PATCH 221/424] deploy-ng: Change default model-cache-dir on deployed applications We replace panda3d in the model-cache-dir ConfigVariable with the name from setup.py. --- direct/src/showutil/dist.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 345f4edeb8..e179b422f4 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -315,16 +315,17 @@ class build_apps(distutils.core.Command): ln = ln.strip() useline = True if ln.startswith('#') or not ln: - useline = False - else: - for plugin in check_plugins: - if plugin in ln and plugin not in self.plugins: - useline = False - if warn_on_missing_plugin: - self.warn( - "Missing plugin ({0}) referenced in user PRC data".format(plugin) - ) - break + continue + if 'model-cache-dir' in ln: + ln = ln.replace('/panda3d', '/{}'.format(self.distribution.get_name())) + for plugin in check_plugins: + if plugin in ln and plugin not in self.plugins: + useline = False + if warn_on_missing_plugin: + self.warn( + "Missing plugin ({0}) referenced in user PRC data".format(plugin) + ) + break if useline: out.append(ln) return out From 77f33f36421284eaa3109a005186cfd3322acfda Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 4 Apr 2018 21:32:44 +0200 Subject: [PATCH 222/424] device: never use inotify_init1(), it is not in older kernels --- panda/src/device/linuxInputDeviceManager.cxx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 8278edd226..114ac144e3 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -32,12 +32,8 @@ LinuxInputDeviceManager:: LinuxInputDeviceManager() { // Use inotify to watch /dev/input for hotplugging of devices. -#if !defined(__ANDROID_API__) || __ANDROID_API__ >= 21 - _inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); -#else _inotify_fd = inotify_init(); fcntl(_inotify_fd, O_NONBLOCK | O_CLOEXEC); -#endif if (_inotify_fd < 0) { device_cat.error() From ff667282126373dd4753a86aa0def0a02ef86e61 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 4 Apr 2018 19:11:03 -0700 Subject: [PATCH 223/424] LinuxInputDeviceManager: Fix fcntl() call --- panda/src/device/linuxInputDeviceManager.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 114ac144e3..2b19502592 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -33,7 +33,8 @@ LinuxInputDeviceManager:: LinuxInputDeviceManager() { // Use inotify to watch /dev/input for hotplugging of devices. _inotify_fd = inotify_init(); - fcntl(_inotify_fd, O_NONBLOCK | O_CLOEXEC); + fcntl(_inotify_fd, F_SETFL, O_NONBLOCK); + fcntl(_inotify_fd, F_SETFD, FD_CLOEXEC); if (_inotify_fd < 0) { device_cat.error() From 664d628cbcbf2428bf6158eb940ca1e4b6a97b75 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 5 Apr 2018 21:04:40 +0200 Subject: [PATCH 224/424] device: D-Pad mappings for DualShock 3 driver on Linux --- panda/src/device/evdevInputDevice.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index cae448c1f3..5e2be61a95 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -971,15 +971,19 @@ map_button(int code, DeviceClass device_class) { case BTN_THUMBR: return GamepadButton::rstick(); + case BTN_DPAD_LEFT: case BTN_TRIGGER_HAPPY1: return GamepadButton::dpad_left(); + case BTN_DPAD_RIGHT: case BTN_TRIGGER_HAPPY2: return GamepadButton::dpad_right(); + case BTN_DPAD_UP: case BTN_TRIGGER_HAPPY3: return GamepadButton::dpad_up(); + case BTN_DPAD_DOWN: case BTN_TRIGGER_HAPPY4: return GamepadButton::dpad_down(); From 023669c8c27d7b8069e16b9f6a7f8887e8a87007 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 8 Apr 2018 17:09:54 +0200 Subject: [PATCH 225/424] deploy-ng: fix absolute exclude patterns, "build" directory exclusion --- direct/src/showutil/dist.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index e179b422f4..5b40b7145f 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -486,17 +486,27 @@ class build_apps(distutils.core.Command): ignore_copy_list += self.exclude_patterns ignore_copy_list += freezer_modpaths ignore_copy_list += self.extra_prc_files - ignore_copy_list = [p3d.GlobPattern(i) for i in ignore_copy_list] + ignore_copy_list = [p3d.GlobPattern(p3d.Filename.from_os_specific(i).get_fullpath()) for i in ignore_copy_list] include_copy_list = [p3d.GlobPattern(i) for i in self.include_patterns] def check_pattern(src, pattern_list): # Normalize file paths across platforms - path = p3d.Filename.from_os_specific(os.path.normpath(src)).get_fullpath() + fn = p3d.Filename.from_os_specific(os.path.normpath(src)) + path = fn.get_fullpath() + fn.make_absolute() + abspath = fn.get_fullpath() + for pattern in pattern_list: - #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches(path))) - if pattern.matches(path): - return True + # If the pattern is absolute, match against the absolute filename. + if pattern.pattern[0] == '/': + #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches(abspath))) + if pattern.matches(abspath): + return True + else: + #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches(path))) + if pattern.matches(path): + return True return False def check_file(fname): From f5932bf0704679e47d0a66729feb91404e2b900e Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 8 Apr 2018 17:26:39 +0200 Subject: [PATCH 226/424] deploy-ng: fix MAIN_DIR when running from outside the main directory --- dtool/src/prc/configPageManager.cxx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index 2ad8f6596d..53f541cbd8 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -129,8 +129,14 @@ reload_implicit_pages() { blobinfo = nullptr; } - if (blobinfo != nullptr && blobinfo->num_pointers >= 11 && blobinfo->main_dir != nullptr) { - ExecutionEnvironment::shadow_environment_variable("MAIN_DIR", blobinfo->main_dir); + if (blobinfo != nullptr) { + if (blobinfo->num_pointers >= 11 && blobinfo->main_dir != nullptr) { + ExecutionEnvironment::set_environment_variable("MAIN_DIR", blobinfo->main_dir); + } else { + // Make sure that py_panda.cxx won't override MAIN_DIR. + ExecutionEnvironment::set_environment_variable("MAIN_DIR", + ExecutionEnvironment::get_environment_variable("MAIN_DIR")); + } } // PRC_PATTERNS lists one or more filename templates separated by spaces. From 742c143a01d97ecf341a7b7fad117b4cca83cf4f Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 8 Apr 2018 17:57:48 +0200 Subject: [PATCH 227/424] deploy-ng: support logging via log_filename (and log_append) setting --- direct/src/showutil/FreezeTool.py | 12 +- direct/src/showutil/dist.py | 21 ++- dtool/src/prc/configPageManager.cxx | 1 + makepanda/makepanda.py | 4 +- pandatool/src/deploy-stub/deploy-stub.c | 217 ++++++++++++++++++++++++ samples/asteroids/setup.py | 2 + 6 files changed, 251 insertions(+), 6 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index c218bc8e41..e944c50cb2 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1642,7 +1642,8 @@ class Freezer: return target - def generateRuntimeFromStub(self, target, stub_file, use_console, fields={}): + def generateRuntimeFromStub(self, target, stub_file, use_console, fields={}, + log_append=False): # We must have a __main__ module to make an exe file. if not self.__writingModule('__main__'): message = "Can't generate an executable without a __main__ module." @@ -1735,7 +1736,7 @@ class Freezer: # Determine the format of the header and module list entries depending # on the platform. - num_pointers = 11 + num_pointers = 12 stub_data = bytearray(stub_file.read()) bitnesses = self._get_executable_bitnesses(stub_data) @@ -1808,6 +1809,10 @@ class Freezer: # A null entry marks the end of the module table. blob += struct.pack(entry_layout, 0, 0, 0) + flags = 0 + if log_append: + flags |= 1 + # Compose the header we will be writing to the stub, to tell it # where to find the module data blob, as well as other variables. header = struct.pack(header_layout, @@ -1816,7 +1821,7 @@ class Freezer: 1, # Version number num_pointers, # Number of pointers that follow 0, # Codepage, not yet used - 0, # Flags, not yet used + flags, table_offset, # Module table pointer. # The following variables need to be set before static init # time. See configPageManager.cxx, where they are read. @@ -1830,6 +1835,7 @@ class Freezer: field_offsets.get('prc_executable_patterns', 0), field_offsets.get('prc_executable_args_envvar', 0), field_offsets.get('main_dir', 0), + field_offsets.get('log_filename', 0), 0) # Now, find the location of the 'blobinfo' symbol in the binary, diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 5b40b7145f..9a143ce513 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -11,6 +11,7 @@ import shutil import struct import io import imp +import string import distutils.core import distutils.log @@ -86,6 +87,8 @@ class build_apps(distutils.core.Command): self.extra_prc_files = [] self.extra_prc_data = '' self.default_prc_dir = None + self.log_filename = None + self.log_append = False self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] self.file_handlers= { @@ -382,7 +385,9 @@ class build_apps(distutils.core.Command): 'prc_encryption_key': None, 'prc_executable_patterns': None, 'prc_executable_args_envvar': None, - }) + 'main_dir': None, + 'log_filename': self.expand_path(self.log_filename, platform), + }, self.log_append) stub_file.close() # Copy the dependencies. @@ -818,6 +823,20 @@ class build_apps(distutils.core.Command): return deps + def expand_path(self, path, platform): + "Substitutes variables in the given path string." + + if path is None: + return None + + t = string.Template(path) + if platform.startswith('win'): + return t.substitute(HOME='~', USER_APPDATA='~/AppData/Local') + elif platform.startswith('macosx'): + return t.substitute(HOME='~', USER_APPDATA='~/Documents') + else: + return t.substitute(HOME='~', USER_APPDATA='~/.local/share') + class bdist_apps(distutils.core.Command): DEFAULT_INSTALLERS = { diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index 53f541cbd8..2f584bd0c5 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -115,6 +115,7 @@ reload_implicit_pages() { const char *prc_executable_patterns; const char *prc_executable_args_envvar; const char *main_dir; + const char *log_filename; }; #ifdef _MSC_VER const BlobInfo *blobinfo = (const BlobInfo *)GetProcAddress(GetModuleHandle(NULL), "blobinfo"); diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 93d43b7935..e893e1711d 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6712,12 +6712,12 @@ if PkgSkip("PYTHON") == 0: TargetAdd('deploy-stub.exe', input='deploy-stub.obj') if GetTarget() == 'windows': TargetAdd('deploy-stub.exe', input='frozen_dllmain.obj') - TargetAdd('deploy-stub.exe', opts=['PYTHON', 'DEPLOYSTUB', 'NOICON']) + TargetAdd('deploy-stub.exe', opts=['WINSHELL', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) if GetTarget() == 'windows': TargetAdd('deploy-stubw.exe', input='deploy-stub.obj') TargetAdd('deploy-stubw.exe', input='frozen_dllmain.obj') - TargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) + TargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'WINSHELL', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) elif GetTarget() == 'darwin': DefSymbol('MACOS_APP_BUNDLE', 'MACOS_APP_BUNDLE') OPTS = OPTS + ['MACOS_APP_BUNDLE'] diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index dc05a67171..230f799a07 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -3,8 +3,10 @@ #include "Python.h" #ifdef _WIN32 # include "malloc.h" +# include #else # include +# include #endif #ifdef __FreeBSD__ @@ -18,6 +20,7 @@ #include #include +#include #if PY_MAJOR_VERSION >= 3 # include @@ -31,6 +34,11 @@ other pointers that are being read by configPageManager.cxx. */ #define MAX_NUM_POINTERS 24 +/* Stored in the flags field of the blobinfo structure below. */ +enum Flags { + F_log_append = 1, +}; + /* Define an exposed symbol where we store the offset to the module data. */ #ifdef _MSC_VER __declspec(dllexport) @@ -113,6 +121,206 @@ static void set_main_dir(char *main_dir) { } } +/** + * Creates the parent directories of the given path. Returns 1 on success. + */ +#ifdef _WIN32 +static int mkdir_parent(const wchar_t *path) { + // Copy the path to a temporary buffer. + wchar_t buffer[4096]; + size_t buflen = wcslen(path); + if (buflen + 1 >= _countof(buffer)) { + return 0; + } + wcscpy_s(buffer, _countof(buffer), path); + + // Seek back to find the last path separator. + while (buflen-- > 0) { + if (buffer[buflen] == '/' || buffer[buflen] == '\\') { + buffer[buflen] = 0; + break; + } + } + if (buflen == 0) { + // There was no path separator. + return 0; + } + + if (CreateDirectoryW(buffer, NULL) != 0) { + // Success! + return 1; + } + + // Failed. + DWORD last_error = GetLastError(); + if (last_error == ERROR_ALREADY_EXISTS) { + // Not really an error: the directory is already there. + return 1; + } + + if (last_error == ERROR_PATH_NOT_FOUND) { + // We need to make the parent directory first. + if (mkdir_parent(buffer)) { + // Parent successfully created. Try again to make the child. + if (CreateDirectoryW(buffer, NULL) != 0) { + // Got it! + return 1; + } + } + } + return 0; +} +#else +static int mkdir_parent(const char *path) { + // Copy the path to a temporary buffer. + char buffer[4096]; + size_t buflen = strlen(path); + if (buflen + 1 >= sizeof(buffer)) { + return 0; + } + strcpy(buffer, path); + + // Seek back to find the last path separator. + while (buflen-- > 0) { + if (buffer[buflen] == '/') { + buffer[buflen] = 0; + break; + } + } + if (buflen == 0) { + // There was no path separator. + return 0; + } + if (mkdir(buffer, 0755) == 0) { + // Success! + return 1; + } + + // Failed. + if (errno == EEXIST) { + // Not really an error: the directory is already there. + return 1; + } + + if (errno == ENOENT || errno == EACCES) { + // We need to make the parent directory first. + if (mkdir_parent(buffer)) { + // Parent successfully created. Try again to make the child. + if (mkdir(buffer, 0755) == 0) { + // Got it! + return 1; + } + } + } + return 0; +} +#endif + +/** + * Redirects the output streams to point to the log file with the given path. + * + * @param path specifies the location of log file, may start with ~ + * @param append should be nonzero if it should not truncate the log file. + */ +static int setup_logging(const char *path, int append) { +#ifdef _WIN32 + // Does it start with a tilde? Perform tilde expansion if so. + wchar_t pathw[MAX_PATH * 2]; + size_t offset = 0; + if (path[0] == '~' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) { + // Strip off the tilde. + ++path; + + // Get the home directory path for the current user. + if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathw))) { + return 0; + } + offset = wcslen(pathw); + } + + // We need to convert the rest of the path from UTF-8 to UTF-16. + if (MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw + offset, + (int)(_countof(pathw) - offset)) == 0) { + return 0; + } + + DWORD access = append ? FILE_APPEND_DATA : (GENERIC_READ | GENERIC_WRITE); + int creation = append ? OPEN_ALWAYS : CREATE_ALWAYS; + HANDLE handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ, + NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle == INVALID_HANDLE_VALUE) { + // Make the parent directories first. + mkdir_parent(pathw); + handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ, + NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); + } + + if (handle == INVALID_HANDLE_VALUE) { + return 0; + } + + if (append) { + SetFilePointer(handle, 0, NULL, FILE_END); + } + + fflush(stdout); + fflush(stderr); + + int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | (append ? _O_APPEND : 0)); + SetStdHandle(STD_OUTPUT_HANDLE, handle); + _dup2(fd, 1); + + SetStdHandle(STD_ERROR_HANDLE, handle); + _dup2(fd, 2); + + _close(fd); + return 1; +#else + // Does it start with a tilde? Perform tilde expansion if so. + char buffer[PATH_MAX * 2]; + size_t offset = 0; + if (path[0] == '~' && (path[1] == 0 || path[1] == '/')) { + // Strip off the tilde. + ++path; + + // Get the home directory path for the current user. + const char *home_dir = getenv("HOME"); + if (home_dir == NULL) { + home_dir = getpwuid(getuid())->pw_dir; + } + offset = strlen(home_dir); + assert(offset < sizeof(buffer)); + strncpy(buffer, home_dir, sizeof(buffer)); + } + + // Copy over the rest of the path. + strcpy(buffer + offset, path); + + mode_t mode = O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC); + int fd = open(buffer, mode, 0644); + if (fd == -1) { + // Make the parent directories first. + mkdir_parent(buffer); + fd = open(buffer, mode, 0644); + } + + if (fd == -1) { + perror(buffer); + return 0; + } + + fflush(stdout); + fflush(stderr); + + dup2(fd, 1); + dup2(fd, 2); + + close(fd); + return 1; +#endif +} + /* Main program */ #ifdef WIN_UNICODE @@ -389,7 +597,9 @@ int main(int argc, char *argv[]) { #endif int retval; struct _frozen *moddef; + const char *log_filename; void *blob = NULL; + log_filename = NULL; /* printf("blob_offset: %d\n", (int)blobinfo.blob_offset); @@ -418,6 +628,9 @@ int main(int argc, char *argv[]) { blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob); } } + if (blobinfo.num_pointers >= 12) { + log_filename = blobinfo.pointers[11]; + } } else { blobinfo.pointers[0] = blob; } @@ -434,6 +647,10 @@ int main(int argc, char *argv[]) { } } + if (log_filename != NULL) { + setup_logging(log_filename, (blobinfo.flags & F_log_append) != 0); + } + #ifdef _WIN32 if (blobinfo.codepage != 0) { SetConsoleCP(blobinfo.codepage); diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index c592c0911f..8f7fd76902 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -12,6 +12,8 @@ setup( 'gui_apps': { 'asteroids': 'main.py', }, + 'log_filename': '$USER_APPDATA/Asteroids/output.log', + 'log_append': False, 'plugins': [ 'pandagl', 'p3openal_audio', From f47857550f50729bd0cbb85683262448e0222e43 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 10 Apr 2018 12:57:20 +0200 Subject: [PATCH 228/424] pefile: fix get_export_address for ordinals > 0 [skip ci] --- direct/src/showutil/pefile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/pefile.py b/direct/src/showutil/pefile.py index 4617a4e488..8e2b5723d1 100755 --- a/direct/src/showutil/pefile.py +++ b/direct/src/showutil/pefile.py @@ -618,7 +618,7 @@ class PEFile(object): name = _unpack_zstring(self.vmem, name_rva) if name == symbol_name: assert ordinal >= 0 and ordinal < expdir.nentries - start = expdir.entries + 8 * ordinal + start = expdir.entries + 4 * ordinal addr, = unpack(' Date: Mon, 30 Apr 2018 18:50:42 -0700 Subject: [PATCH 229/424] deploy-ng: Fix running against pip 10.0+ --- direct/src/showutil/dist.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 9a143ce513..b2b3b1742e 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -175,7 +175,13 @@ class build_apps(distutils.core.Command): self.announce('Gathering wheels for platform: {}'.format(platform), distutils.log.INFO) whldir = os.path.join(self.build_base, '__whl_cache__') - abi_tag = pip.pep425tags.get_abi_tag() + + #TODO find a better way to get abi tag than from internal/private pip APIs + if hasattr(pip, 'pep425tags'): + abi_tag = pip.pep425tags.get_abi_tag() + else: + from pip._internal import pep425tags + abi_tag = pep425tags.get_abi_tag() if 'u' in abi_tag and (platform.startswith('win') or platform.startswith('macosx')): abi_tag = abi_tag.replace('u', '') @@ -196,7 +202,7 @@ class build_apps(distutils.core.Command): for index in self.pypi_extra_indexes: pip_args += ['--extra-index-url', index] - pip.main(args=pip_args) + subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args) wheelpaths = [os.path.join(whldir,i) for i in os.listdir(whldir) if platform in i] return wheelpaths From 7f974865d5537858b52513362b7902ceab6c229c Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 30 Apr 2018 20:27:31 -0700 Subject: [PATCH 230/424] deploy-ng: Subclass from setuptools.Command instead of distutils.core.Command --- direct/src/showutil/dist.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index b2b3b1742e..964326bf9c 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -13,7 +13,7 @@ import io import imp import string -import distutils.core +import setuptools import distutils.log from . import FreezeTool @@ -64,7 +64,7 @@ macosx_binary_magics = ( b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA') -class build_apps(distutils.core.Command): +class build_apps(setuptools.Command): description = 'build Panda3D applications' user_options = [ ('build-base=', None, 'directory to build applications in'), @@ -844,7 +844,7 @@ class build_apps(distutils.core.Command): return t.substitute(HOME='~', USER_APPDATA='~/.local/share') -class bdist_apps(distutils.core.Command): +class bdist_apps(setuptools.Command): DEFAULT_INSTALLERS = { 'manylinux1_x86_64': ['gztar'], 'manylinux1_i386': ['gztar'], From 52360dc10c299f080ca21fde60b45f42e967779a Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 30 Apr 2018 20:31:54 -0700 Subject: [PATCH 231/424] deploy-ng: Add bztar and xztar installers --- direct/src/showutil/dist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 964326bf9c..d85bf7a071 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -882,14 +882,14 @@ class bdist_apps(setuptools.Command): for installer in installers: self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO) - if installer in ('zip', 'gztar'): + if installer in ('zip', 'gztar', 'bztar', 'xztar'): base_dir = self.distribution.get_name() temp_dir = os.path.join(build_base, base_dir) if (os.path.exists(temp_dir)): shutil.rmtree(temp_dir) shutil.copytree(build_dir, temp_dir) - distutils.archive_util.make_archive(basename, installer, root_dir=build_base, base_dir=base_dir) + shutil.make_archive(basename, installer, root_dir=build_base, base_dir=base_dir) shutil.rmtree(temp_dir) else: From aaf7ff9a6ce87ef0ed8817cf9808abf04ea72b1b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 5 May 2018 18:10:05 -0700 Subject: [PATCH 232/424] device: Define BTN_DPAD* if not present --- panda/src/device/evdevInputDevice.cxx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 5e2be61a95..cf0cc18cc6 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -23,6 +23,14 @@ #include #include +#ifndef BTN_DPAD_UP +#define BTN_DPAD_UP 0x220 +#define BTN_DPAD_DOWN 0x221 +#define BTN_DPAD_LEFT 0x222 +#define BTN_DPAD_RIGHT 0x223 +#endif + + // Android introduces these in API level 21. #ifndef BTN_TRIGGER_HAPPY #define BTN_TRIGGER_HAPPY 0x2c0 From 82d32e1f14c4aeaaa5c4e8faa96dacf9e1745ae6 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 6 May 2018 09:46:59 -0700 Subject: [PATCH 233/424] deploy-ng: Fix using lists in dictionaries when using setup.cfg --- direct/src/showutil/dist.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index d85bf7a071..949331ac80 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -131,8 +131,14 @@ class build_apps(setuptools.Command): self.rename_paths = _parse_dict(self.rename_paths) self.include_patterns = _parse_list(self.include_patterns) self.exclude_patterns = _parse_list(self.exclude_patterns) - self.include_modules = _parse_dict(self.include_modules) - self.exclude_modules = _parse_dict(self.exclude_modules) + self.include_modules = { + key: _parse_list(value) + for key, value in _parse_dict(self.include_modules).items() + } + self.exclude_modules = { + key: _parse_list(value) + for key, value in _parse_dict(self.exclude_modules).items() + } self.plugins = _parse_list(self.plugins) self.extra_prc_files = _parse_list(self.extra_prc_files) From 081ceb4329a15e4d16628e4ea4b07f1c34d7df33 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 6 May 2018 09:50:17 -0700 Subject: [PATCH 234/424] deploy-ng: Fix parsing platforms from setup.cfg --- direct/src/showutil/dist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 949331ac80..ae63b14432 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -139,6 +139,7 @@ class build_apps(setuptools.Command): key: _parse_list(value) for key, value in _parse_dict(self.exclude_modules).items() } + self.platforms = _parse_list(self.platforms) self.plugins = _parse_list(self.plugins) self.extra_prc_files = _parse_list(self.extra_prc_files) From 7eeb4167658c43ca1938a1080b7eb056a34bf1d4 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 6 May 2018 10:24:55 -0700 Subject: [PATCH 235/424] deploy-ng: Remove debug print --- direct/src/showutil/dist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index ae63b14432..871d4c9ba2 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -884,7 +884,6 @@ class bdist_apps(setuptools.Command): build_dir = os.path.join(build_base, platform) basename = '{}_{}'.format(self.distribution.get_fullname(), platform) installers = self.installers.get(platform, self.DEFAULT_INSTALLERS.get(platform, ['zip'])) - print(installers) for installer in installers: self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO) From 7073609480edf9489749f3eba5da3ad9b52e3e34 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 6 May 2018 10:25:32 -0700 Subject: [PATCH 236/424] deploy-ng: Use sys.executable instead of 'python' in tests/build_samples.py --- tests/build_samples.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/build_samples.py b/tests/build_samples.py index 3abd2bfb2f..222a7df354 100644 --- a/tests/build_samples.py +++ b/tests/build_samples.py @@ -1,5 +1,6 @@ import os import subprocess +import sys SAMPLES_TO_BUILD = [ 'asteroids', @@ -13,7 +14,7 @@ def main(): # This will raise a CalledProcessError if the build fails, which will cause # this script to fail - subprocess.check_call(['python', 'setup.py', 'bdist_apps']) + subprocess.check_call([sys.executable, 'setup.py', 'bdist_apps']) if __name__ == '__main__': main() From cb077fcdaeb6b7657111df60bd2f770036b8f46d Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 6 May 2018 10:54:35 -0700 Subject: [PATCH 237/424] deploy-ng: Add panda3d.dist-info to installers --- makepanda/installer.nsi | 2 ++ makepanda/installpanda.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/makepanda/installer.nsi b/makepanda/installer.nsi index ccf3b5b1b7..b21265ad20 100644 --- a/makepanda/installer.nsi +++ b/makepanda/installer.nsi @@ -367,6 +367,8 @@ SectionGroup "Python support" File /r "${BUILT}\pandac\input\*" SetOutPath $INSTDIR\Pmw File /r /x CVS "${BUILT}\Pmw\*" + SetOutPath $INSTDIR\panda3d.dist-info + File /nonfatal /r "${BUILT}\panda3d.dist-info\*" !ifdef REGVIEW SetRegView ${REGVIEW} diff --git a/makepanda/installpanda.py b/makepanda/installpanda.py index 63a4601ee6..c7c6291f66 100644 --- a/makepanda/installpanda.py +++ b/makepanda/installpanda.py @@ -211,6 +211,8 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir( oscmd("cp doc/ReleaseNotes "+destdir+prefix+"/share/panda3d/ReleaseNotes") oscmd("echo '"+prefix+"/share/panda3d' > "+destdir+PPATH+"/panda3d.pth") oscmd("echo '"+libdir+"/panda3d'>> "+destdir+PPATH+"/panda3d.pth") + if os.path.isdir(outputdir+"/panda3d.dist-info"): + oscmd("cp -R "+outputdir+"/panda3d.dist-info "+destdir+PPATH) if (sys.platform.startswith("freebsd")): oscmd("echo '"+libdir+"/panda3d'> "+destdir+"/usr/local/libdata/ldconfig/panda3d") else: From ea351da50cf970098d0586e1f9796bf80b599e1a Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 May 2018 15:30:10 +0200 Subject: [PATCH 238/424] deploy-ng: fix issue with irrelevant wheels in cache dir being used This is still not a perfect solution, since eg. the wrong version of a package may still be used if older versions are still in the __whl_cache__ directory. We may want to look into using pip to resolve the package versions, or do something clever such as automatically remove cached wheels if a newer version of the same package was downloaded. --- direct/src/showutil/dist.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 871d4c9ba2..e916a4d316 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -185,10 +185,12 @@ class build_apps(setuptools.Command): #TODO find a better way to get abi tag than from internal/private pip APIs if hasattr(pip, 'pep425tags'): - abi_tag = pip.pep425tags.get_abi_tag() + pep425tags = pip.pep425tags + wheel = pip.wheel else: - from pip._internal import pep425tags - abi_tag = pep425tags.get_abi_tag() + from pip._internal import pep425tags, wheel + + abi_tag = pep425tags.get_abi_tag() if 'u' in abi_tag and (platform.startswith('win') or platform.startswith('macosx')): abi_tag = abi_tag.replace('u', '') @@ -211,7 +213,18 @@ class build_apps(setuptools.Command): subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args) - wheelpaths = [os.path.join(whldir,i) for i in os.listdir(whldir) if platform in i] + # Now figure out which of the downloaded wheels are relevant to us. + tags = pep425tags.get_supported(platform=platform, abi=abi_tag) + wheelpaths = [] + for filename in os.listdir(whldir): + try: + whl = wheel.Wheel(filename) + except wheel.InvalidWheelFilename: + continue + + if whl.supported(tags): + wheelpaths.append(os.path.join(whldir, filename)) + return wheelpaths def bundle_macos_app(self, builddir): @@ -278,7 +291,7 @@ class build_apps(setuptools.Command): wheelpaths = self.download_wheels(platform) for whl in wheelpaths: - if 'panda3d-' in whl: + if os.path.basename(whl).startswith('panda3d-'): p3dwhlfn = whl p3dwhl = self._get_zip_file(p3dwhlfn) break From e292fedd913593dc4399b357e476ef7a9a22b1d5 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 May 2018 22:56:44 +0200 Subject: [PATCH 239/424] deploy-ng: add .libs directory in .whl to search path This seems to be a convention imposed by auditwheel and used by numpy. --- direct/src/showutil/dist.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index e916a4d316..6017831261 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -504,6 +504,14 @@ class build_apps(setuptools.Command): if use_wheels: search_path.append(os.path.join(p3dwhlfn, 'deploy_libs')) + # If the .whl containing this file has a .libs directory, add + # it to the path. This is an auditwheel/numpy convention. + if '.whl' + os.sep in source_path: + whl, wf = source_path.split('.whl' + os.path.sep) + whl += '.whl' + rootdir = wf.split(os.path.sep, 1)[0] + search_path.append(os.path.join(whl, rootdir, '.libs')) + target_path = os.path.join(builddir, basename) self.copy_with_dependencies(source_path, target_path, search_path) From 229e1a1b1f8d6fe09022cd13424c94203689c7dc Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 May 2018 23:26:55 +0200 Subject: [PATCH 240/424] deploy-ng: fix building a Linux build from Python 2.7 on Windows --- direct/src/showutil/dist.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 6017831261..35da4b6129 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -195,6 +195,10 @@ class build_apps(setuptools.Command): if 'u' in abi_tag and (platform.startswith('win') or platform.startswith('macosx')): abi_tag = abi_tag.replace('u', '') + # For these distributions, we need to append 'u' on Linux + if abi_tag in ('cp26m', 'cp27m', 'cp32m') and not platform.startswith('win') and not platform.startswith('macosx'): + abi_tag += 'u' + pip_version = pip.__version__.split('.') if int(pip_version[0]) < 9: raise RuntimeError("pip 9.0 or greater is required, but found {}".format(pip.__version__)) From 126f4bc418de9113acaef8bf1afc8e64501713bd Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 May 2018 23:30:14 +0200 Subject: [PATCH 241/424] deploy-ng: copy over data directories for matplotlib --- direct/src/showutil/dist.py | 57 +++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 35da4b6129..c3015a85e5 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -9,7 +9,6 @@ import subprocess import zipfile import shutil import struct -import io import imp import string @@ -63,6 +62,13 @@ macosx_binary_magics = ( b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA', b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA') +# Some dependencies need data directories to be extracted. Key is a module +# (or package) name that triggers it to be included, the value is a dict +# mapping source directories (use / slashes) to target directories. +PACKAGE_DATA_DIRS = { + 'matplotlib': {'matplotlib/mpl-data': 'mpl-data'}, +} + class build_apps(setuptools.Command): description = 'build Panda3D applications' @@ -519,6 +525,30 @@ class build_apps(setuptools.Command): target_path = os.path.join(builddir, basename) self.copy_with_dependencies(source_path, target_path, search_path) + # Extract any other data files from dependency packages. + for module, paths in PACKAGE_DATA_DIRS.items(): + if module not in freezer_modules: + pass + + self.announce('Copying data files for module: {}'.format(module), distutils.log.INFO) + + # OK, find out in which .whl this occurs. + for whl in wheelpaths: + whlfile = self._get_zip_file(whl) + filenames = whlfile.namelist() + for source_dir, target_dir in paths.items(): + # Relocate the target dir to the build directory. + target_dir = target_dir.replace('/', os.sep) + target_dir = os.path.join(builddir, target_dir) + + for wf in filenames: + if wf.lower().startswith(source_dir.lower() + '/'): + wf = wf.replace('/', os.sep) + relpath = wf[len(source_dir) + 1:] + source_path = os.path.join(whl, wf) + target_path = os.path.join(target_dir, relpath) + self.copy(source_path, target_path) + # Copy Game Files self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO) ignore_copy_list = [ @@ -658,10 +688,8 @@ class build_apps(setuptools.Command): self.warn("could not find dependency {0} (referenced by {1})".format(name, referenced_by)) self.exclude_dependencies.append(name.lower()) - def copy_with_dependencies(self, source_path, target_path, search_path): - """ Copies source_path to target_path. It also scans source_path for - any dependencies, which are located along the given search_path and - copied to the same directory as target_path. + def copy(self, source_path, target_path): + """ Copies source_path to target_path. source_path may be located inside a .whl file. """ @@ -671,6 +699,11 @@ class build_apps(setuptools.Command): # No relative path (e.g., files on different drives in Windows), just print absolute paths instead self.announce('copying {0} -> {1}'.format(source_path, target_path)) + # Make the directory if it does not yet exist. + target_dir = os.path.dirname(target_path) + if not os.path.isdir(target_dir): + os.makedirs(target_dir) + # Copy the file, and open it for analysis. if '.whl' in source_path: # This was found in a wheel, extract it @@ -680,14 +713,20 @@ class build_apps(setuptools.Command): data = whlfile.read(wf.replace(os.path.sep, '/')) with open(target_path, 'wb') as f: f.write(data) - # Wrap the data in a BytesIO, since we need to be able to seek in - # the file; the stream returned by whlfile.open won't let us seek. - fp = io.BytesIO(data) else: # Regular file, copy it shutil.copyfile(source_path, target_path) - fp = open(target_path, 'rb') + def copy_with_dependencies(self, source_path, target_path, search_path): + """ Copies source_path to target_path. It also scans source_path for + any dependencies, which are located along the given search_path and + copied to the same directory as target_path. + + source_path may be located inside a .whl file. """ + + self.copy(source_path, target_path) + + fp = open(target_path, 'rb') target_dir = os.path.dirname(target_path) base = os.path.basename(target_path) self.copy_dependencies(fp, target_dir, search_path, base) From f34d4c48517aee584cb9a09e1653c99a2f16efd2 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 May 2018 23:32:46 +0200 Subject: [PATCH 242/424] deploy-ng: add hidden imports for matplotlib --- direct/src/showutil/FreezeTool.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index e944c50cb2..5acfb0b1f9 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -75,8 +75,15 @@ hiddenImports = { 'xml.etree.cElementTree': ['xml.etree.ElementTree'], 'datetime': ['_strptime'], 'keyring.backends': ['keyring.backends.*'], + 'matplotlib.font_manager': ['encodings.mac_roman'], } +if sys.version_info >= (3,): + hiddenImports['matplotlib.backends._backend_tk'] = ['tkinter'] +else: + hiddenImports['matplotlib.backends._backend_tk'] = ['Tkinter'] + + # These are missing modules that we've reported already this session. reportedMissing = {} From ef69e8935fe720553b3e2dd485840535ed49880d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 May 2018 23:35:00 +0200 Subject: [PATCH 243/424] freezer: add mechanism for overriding modules with custom code --- direct/src/showutil/FreezeTool.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 5acfb0b1f9..1e3c0e61c6 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -84,6 +84,10 @@ else: hiddenImports['matplotlib.backends._backend_tk'] = ['Tkinter'] +# These are overrides for specific modules. +overrideModules = { +} + # These are missing modules that we've reported already this session. reportedMissing = {} @@ -2245,9 +2249,16 @@ class PandaModuleFinder(modulefinder.ModuleFinder): m = self.load_package(fqname, pathname) self.msgout(2, "load_module ->", m) return m + if type == imp.PY_SOURCE: - newline = b'\n' if 'b' in mode else '\n' - co = compile(fp.read()+newline, pathname, 'exec') + if fqname in overrideModules: + # This module has a custom override. + code = overrideModules[fqname] + else: + code = fp.read() + + code += b'\n' if isinstance(code, bytes) else '\n' + co = compile(code, pathname, 'exec') elif type == imp.PY_COMPILED: try: marshal_data = importlib._bootstrap_external._validate_bytecode_header(fp.read()) @@ -2257,6 +2268,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): co = marshal.loads(marshal_data) else: co = None + m = self.add_module(fqname) m.__file__ = pathname if co: @@ -2283,6 +2295,10 @@ class PandaModuleFinder(modulefinder.ModuleFinder): if fullname in self.excludes: raise ImportError(name) + # If we have a custom override for this module, we know we have it. + if fullname in overrideModules: + return (None, '', ('.py', 'r', imp.PY_SOURCE)) + # If no search path is given, look for a built-in module. if path is None: if name in sys.builtin_module_names: From 5322811e0864c065aa909a725dd0f16998ea6b55 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 May 2018 23:35:34 +0200 Subject: [PATCH 244/424] deploy-ng: be lenient when processing DLLs with invalid codepage --- direct/src/showutil/pefile.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/direct/src/showutil/pefile.py b/direct/src/showutil/pefile.py index 8e2b5723d1..548c09e1b7 100755 --- a/direct/src/showutil/pefile.py +++ b/direct/src/showutil/pefile.py @@ -112,11 +112,18 @@ class DataResource(object): self.data = None self.code_page = 0 - def get_data(self): - if self.code_page: - return self.data.encode('cp%d' % self.code_page) + @property + def encoding(self): + if self.code_page == 0: + return 'ascii' else: - return self.data + return 'cp%d' % (self.code_page) + + def get_data(self): + return self.data + + def get_text(self, errors='strict'): + return self.data.decode(self.encoding, errors) class IconGroupResource(object): @@ -479,10 +486,6 @@ class ResourceTable(object): entry = IconGroupResource() entry.unpack_from(data) else: - if code_page != 0: - # Decode the data using the provided code page. - data = data.decode("cp%d" % code_page) - entry = DataResource() entry.data = data entry.code_page = code_page From d00109452afc6f6eab77b61911e4638b20b2fee3 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 May 2018 23:37:40 +0200 Subject: [PATCH 245/424] deploy-ng: set __file__ for all frozen modules (using import hooks) Many thirdparty libraries rely on __file__ being set properly, for example to be able to locate data files, so it is easiest to just set this to something remotely sensible (like the executable path). --- direct/src/showutil/FreezeTool.py | 23 ++++++++++++ direct/src/showutil/dist.py | 50 +++++++++++++++++++++++++ pandatool/src/deploy-stub/deploy-stub.c | 2 +- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 1e3c0e61c6..978891c0f1 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -86,6 +86,29 @@ else: # These are overrides for specific modules. overrideModules = { + # Used by the warnings module, among others, to get line numbers. Since + # we set __file__, this would cause it to try and extract Python code + # lines from the main executable, which we don't want. + 'linecache': """__all__ = ["getline", "clearcache", "checkcache"] + +def getline(filename, lineno, module_globals=None): + return '' + +def clearcache(): + pass + +def getlines(filename, module_globals=None): + return [] + +def checkcache(filename=None): + pass + +def updatecache(filename, module_globals=None): + pass + +def lazycache(filename, module_globals): + pass +""", } # These are missing modules that we've reported already this session. diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index c3015a85e5..d4791d6789 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -69,6 +69,55 @@ PACKAGE_DATA_DIRS = { 'matplotlib': {'matplotlib/mpl-data': 'mpl-data'}, } +# site.py for Python 2. +SITE_PY2 = u""" +import sys + +# Override __import__ to set __file__ for frozen modules. +prev_import = __import__ +def __import__(*args, **kwargs): + mod = prev_import(*args, **kwargs) + if mod: + mod.__file__ = sys.executable + return mod + +# Add our custom __import__ version to the global scope, as well as a builtin +# definition for __file__ so that it is available in the module itself. +import __builtin__ +__builtin__.__import__ = __import__ +__builtin__.__file__ = sys.executable +del __builtin__ +""" + +# site.py for Python 3. +SITE_PY3 = u""" +import sys +from _frozen_importlib import _imp, FrozenImporter + +if sys.platform == 'win32': + # Make sure the preferred encoding is something we actually support. + import _bootlocale + enc = _bootlocale.getpreferredencoding().lower() + if enc != 'utf-8' and not _imp.is_frozen('encodings.%s' % (enc)): + def getpreferredencoding(do_setlocale=True): + return 'mbcs' + _bootlocale.getpreferredencoding = getpreferredencoding + +# Alter FrozenImporter to give a __file__ property to frozen modules. +_find_spec = FrozenImporter.find_spec + +def find_spec(fullname, path=None, target=None): + spec = _find_spec(fullname, path=path, target=target) + if spec: + spec.has_location = True + spec.origin = sys.executable + return spec + +FrozenImporter.find_spec = find_spec +""" + +SITE_PY = SITE_PY3 if sys.version_info >= (3,) else SITE_PY2 + class build_apps(setuptools.Command): description = 'build Panda3D applications' @@ -387,6 +436,7 @@ class build_apps(setuptools.Command): def create_runtime(appname, mainscript, use_console): freezer = FreezeTool.Freezer(platform=platform, path=path) freezer.addModule('__main__', filename=mainscript) + freezer.addModule('site', filename='site.py', text=SITE_PY) for incmod in self.include_modules.get(appname, []) + self.include_modules.get('*', []): freezer.addModule(incmod) for exmod in self.exclude_modules.get(appname, []) + self.exclude_modules.get('*', []): diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 230f799a07..cf44de992e 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -361,7 +361,7 @@ int Py_FrozenMain(int argc, char **argv) #endif Py_FrozenFlag = 1; /* Suppress errors from getpath.c */ - Py_NoSiteFlag = 1; + Py_NoSiteFlag = 0; Py_NoUserSiteDirectory = 1; if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0') From b6f29194206da7a4213f2a70f341a2418ea4f9ad Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 13 May 2018 23:39:30 +0200 Subject: [PATCH 246/424] deploy-ng: make Tkinter work for Windows non-cross-builds This is only a temporary solution, since this only supports one platform. We may have to pack the .tcl files into the panda3d .whl archive. --- direct/src/showutil/dist.py | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index d4791d6789..cd4751d33b 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -87,6 +87,21 @@ import __builtin__ __builtin__.__import__ = __import__ __builtin__.__file__ = sys.executable del __builtin__ + +# Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files. +import os +tcl_dir = os.path.join(os.path.dirname(sys.executable), 'tcl') +if os.path.isdir(tcl_dir): + for dir in os.listdir(tcl_dir): + sub_dir = os.path.join(tcl_dir, dir) + if os.path.isdir(sub_dir): + if dir.startswith('tcl'): + os.environ['TCL_LIBRARY'] = sub_dir + if dir.startswith('tk'): + os.environ['TK_LIBRARY'] = sub_dir + if dir.startswith('tix'): + os.environ['TIX_LIBRARY'] = sub_dir +del os """ # site.py for Python 3. @@ -114,6 +129,21 @@ def find_spec(fullname, path=None, target=None): return spec FrozenImporter.find_spec = find_spec + +# Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files. +import os +tcl_dir = os.path.join(os.path.dirname(sys.executable), 'tcl') +if os.path.isdir(tcl_dir): + for dir in os.listdir(tcl_dir): + sub_dir = os.path.join(tcl_dir, dir) + if os.path.isdir(sub_dir): + if dir.startswith('tcl'): + os.environ['TCL_LIBRARY'] = sub_dir + if dir.startswith('tk'): + os.environ['TK_LIBRARY'] = sub_dir + if dir.startswith('tix'): + os.environ['TIX_LIBRARY'] = sub_dir +del os """ SITE_PY = SITE_PY3 if sys.version_info >= (3,) else SITE_PY2 @@ -575,6 +605,23 @@ class build_apps(setuptools.Command): target_path = os.path.join(builddir, basename) self.copy_with_dependencies(source_path, target_path, search_path) + # Copy over the tcl directory. + #TODO: get this to work on non-Windows platforms. + if sys.platform == "win32" and platform.startswith('win'): + tcl_dir = os.path.join(sys.prefix, 'tcl') + tkinter_name = 'tkinter' if sys.version_info >= (3, 0) else 'Tkinter' + + if os.path.isdir(tcl_dir) and tkinter_name in freezer_modules: + self.announce('Copying Tcl files', distutils.log.INFO) + os.makedirs(os.path.join(builddir, 'tcl')) + + for dir in os.listdir(tcl_dir): + sub_dir = os.path.join(tcl_dir, dir) + if os.path.isdir(sub_dir): + target_dir = os.path.join(builddir, 'tcl', dir) + self.announce('copying {0} -> {1}'.format(sub_dir, target_dir)) + shutil.copytree(sub_dir, target_dir) + # Extract any other data files from dependency packages. for module, paths in PACKAGE_DATA_DIRS.items(): if module not in freezer_modules: From 11723b27ee92b69341abed8c38d62144aa6d1ae8 Mon Sep 17 00:00:00 2001 From: rdb Date: Sat, 23 Jun 2018 23:30:44 +0200 Subject: [PATCH 247/424] device: various warning fixes for input-overhaul on Linux --- panda/src/device/evdevInputDevice.cxx | 24 +++++++------- panda/src/device/evdevInputDevice.h | 4 +-- panda/src/device/linuxInputDeviceManager.cxx | 33 ++++++++++---------- panda/src/device/linuxInputDeviceManager.h | 4 +-- panda/src/device/linuxJoystickDevice.cxx | 18 +++++------ panda/src/device/linuxJoystickDevice.h | 4 +-- 6 files changed, 43 insertions(+), 44 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index e4ca7592b2..dec94f803d 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -96,7 +96,7 @@ TypeHandle EvdevInputDevice::_type_handle; * Creates a new device representing the evdev device with the given index. */ EvdevInputDevice:: -EvdevInputDevice(LinuxInputDeviceManager *manager, int index) : +EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index) : _manager(manager), _index(index), _fd(-1), @@ -113,7 +113,7 @@ EvdevInputDevice(LinuxInputDeviceManager *manager, int index) : _rtrigger_code(-1) { char path[64]; - sprintf(path, "/dev/input/event%d", index); + sprintf(path, "/dev/input/event%zd", index); _fd = open(path, O_RDWR | O_NONBLOCK); if (_fd >= 0) { @@ -338,8 +338,8 @@ init_device() { // Test for specific name tags string lowercase_name = _name; - for(int x=0; x<_name.length(); x++) { - lowercase_name[x]=tolower(lowercase_name[x]); + for(size_t x = 0; x < _name.length(); ++x) { + lowercase_name[x] = tolower(lowercase_name[x]); } if (lowercase_name.find("gamepad") != string::npos) { device_scores[DC_gamepad] += 10; @@ -362,7 +362,7 @@ init_device() { } // Check which device type got the most points - size_t highest_score = 0; + int highest_score = 0; for (size_t i = 0; i < DC_COUNT; i++) { if (device_scores[i] > highest_score) { highest_score = device_scores[i]; @@ -406,7 +406,7 @@ init_device() { } _buttons.push_back(button); - if (i >= _button_indices.size()) { + if ((size_t)i >= _button_indices.size()) { _button_indices.resize(i + 1, -1); } _button_indices[i] = button_index; @@ -594,11 +594,11 @@ init_device() { char path[64]; char buffer[256]; const char *parent = ""; - sprintf(path, "/sys/class/input/event%d/device/device/../product", _index); + sprintf(path, "/sys/class/input/event%zd/device/device/../product", _index); FILE *f = fopen(path, "r"); if (!f) { parent = "../"; - sprintf(path, "/sys/class/input/event%d/device/device/%s../product", _index, parent); + sprintf(path, "/sys/class/input/event%zd/device/device/%s../product", _index, parent); f = fopen(path, "r"); } if (f) { @@ -610,7 +610,7 @@ init_device() { } fclose(f); } - sprintf(path, "/sys/class/input/event%d/device/device/%s../manufacturer", _index, parent); + sprintf(path, "/sys/class/input/event%zd/device/device/%s../manufacturer", _index, parent); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -619,7 +619,7 @@ init_device() { } fclose(f); } - sprintf(path, "/sys/class/input/event%d/device/device/%s../serial", _index, parent); + sprintf(path, "/sys/class/input/event%zd/device/device/%s../serial", _index, parent); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -709,7 +709,7 @@ process_events() { button_changed(_dpad_up_button, events[i].value < 0); button_changed(_dpad_up_button+1, events[i].value > 0); } - nassertd(code >= 0 && code < _control_indices.size()) break; + nassertd(code >= 0 && (size_t)code < _control_indices.size()) break; index = _control_indices[code]; if (index >= 0) { control_changed(index, events[i].value); @@ -717,7 +717,7 @@ process_events() { break; case EV_KEY: - nassertd(code >= 0 && code < _button_indices.size()) break; + nassertd(code >= 0 && (size_t)code < _button_indices.size()) break; index = _button_indices[code]; if (index >= 0) { button_changed(index, events[i].value != 0); diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 8f73d04a20..d82ba5f980 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -27,7 +27,7 @@ class LinuxInputDeviceManager; */ class EXPCL_PANDA_DEVICE EvdevInputDevice : public InputDevice { public: - EvdevInputDevice(LinuxInputDeviceManager *manager, int index); + EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index); virtual ~EvdevInputDevice(); private: @@ -41,7 +41,7 @@ private: LinuxInputDeviceManager *_manager; int _fd; - int _index; + size_t _index; bool _can_write; int _ff_id; diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 4591484b11..2e956cb739 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -48,11 +48,11 @@ LinuxInputDeviceManager() { // Scan /dev/input for a list of input devices. DIR *dir = opendir("/dev/input"); if (dir) { - vector_int indices; + std::vector indices; dirent *entry = readdir(dir); while (entry != nullptr) { - int index = -1; - if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%d", &index) == 1) { + size_t index; + if (entry->d_type == DT_CHR && sscanf(entry->d_name, "event%zd", &index) == 1) { indices.push_back(index); } entry = readdir(dir); @@ -64,9 +64,8 @@ LinuxInputDeviceManager() { std::sort(indices.begin(), indices.end()); _evdev_devices.resize(indices.back() + 1, nullptr); - vector_int::const_iterator it; - for (it = indices.begin(); it != indices.end(); ++it) { - consider_add_evdev_device(*it); + for (size_t index : indices) { + consider_add_evdev_device(index); } } else { device_cat.error() @@ -93,7 +92,7 @@ LinuxInputDeviceManager:: * This is the preferred interface for input devices on Linux. */ InputDevice *LinuxInputDeviceManager:: -consider_add_evdev_device(int ev_index) { +consider_add_evdev_device(size_t ev_index) { if (ev_index < _evdev_devices.size()) { if (_evdev_devices[ev_index] != nullptr) { // We already have this device. FIXME: probe it and add it to the @@ -107,7 +106,7 @@ consider_add_evdev_device(int ev_index) { // Check if we can directly read the event device. char path[64]; - sprintf(path, "/dev/input/event%d", ev_index); + sprintf(path, "/dev/input/event%zd", ev_index); if (access(path, R_OK) == 0) { PT(InputDevice) device = new EvdevInputDevice(this, ev_index); @@ -133,7 +132,7 @@ consider_add_evdev_device(int ev_index) { // (notably, force feedback). // We do this by checking for a js# directory inside the sysfs directory. - sprintf(path, "/sys/class/input/event%d/device", ev_index); + sprintf(path, "/sys/class/input/event%zd/device", ev_index); DIR *dir = opendir(path); if (dir == nullptr) { @@ -146,8 +145,8 @@ consider_add_evdev_device(int ev_index) { dirent *entry = readdir(dir); while (entry != nullptr) { - int js_index = -1; - if (sscanf(entry->d_name, "js%d", &js_index) == 1) { + size_t js_index; + if (sscanf(entry->d_name, "js%zd", &js_index) == 1) { // Yes, we fonud a corresponding js device. Try adding it. closedir(dir); @@ -177,9 +176,9 @@ consider_add_evdev_device(int ev_index) { * cannot be accessed. */ InputDevice *LinuxInputDeviceManager:: -consider_add_js_device(int js_index) { +consider_add_js_device(size_t js_index) { char path[64]; - sprintf(path, "/dev/input/js%d", js_index); + sprintf(path, "/dev/input/js%zd", js_index); if (access(path, R_OK) == 0) { PT(LinuxJoystickDevice) device = new LinuxJoystickDevice(this, js_index); @@ -254,8 +253,8 @@ update() { if (event->mask & IN_DELETE) { // The device was deleted. If we have it, remove it. - int index = -1; - if (sscanf(event->name, "event%d", &index) == 1) { + size_t index; + if (sscanf(event->name, "event%zd", &index) == 1) { // Check if we have this evdev device. If so, disconnect it. if (index < _evdev_devices.size()) { PT(InputDevice) device = _evdev_devices[index]; @@ -280,8 +279,8 @@ update() { // to check for the latter since it seems that the device may get the // IN_CREATE event before the driver gets the permissions set properly. - int index = -1; - if (sscanf(event->name, "event%d", &index) == 1) { + size_t index; + if (sscanf(event->name, "event%zd", &index) == 1) { InputDevice *device = consider_add_evdev_device(index); if (device != nullptr && device->is_connected()) { throw_event("connect-device", device); diff --git a/panda/src/device/linuxInputDeviceManager.h b/panda/src/device/linuxInputDeviceManager.h index 61d0bf386a..c5ad6a013f 100644 --- a/panda/src/device/linuxInputDeviceManager.h +++ b/panda/src/device/linuxInputDeviceManager.h @@ -27,8 +27,8 @@ private: LinuxInputDeviceManager(); ~LinuxInputDeviceManager(); - InputDevice *consider_add_evdev_device(int index); - InputDevice *consider_add_js_device(int index); + InputDevice *consider_add_evdev_device(size_t index); + InputDevice *consider_add_js_device(size_t index); virtual void update(); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 5a82197f06..6617fc1dcd 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -28,7 +28,7 @@ TypeHandle LinuxJoystickDevice::_type_handle; * Creates a new device using the Linux joystick device with the given index. */ LinuxJoystickDevice:: -LinuxJoystickDevice(LinuxInputDeviceManager *manager, int index) : +LinuxJoystickDevice(LinuxInputDeviceManager *manager, size_t index) : _manager(manager), _fd(-1), _index(index), @@ -97,7 +97,7 @@ open_device() { nassertr(_lock.debug_is_locked(), false); char path[64]; - sprintf(path, "/dev/input/js%d", _index); + sprintf(path, "/dev/input/js%zd", _index); _fd = open(path, O_RDONLY | O_NONBLOCK); @@ -287,7 +287,7 @@ open_device() { } // Get additional information from sysfs. - sprintf(path, "/sys/class/input/js%d/device/id/vendor", _index); + sprintf(path, "/sys/class/input/js%zd/device/id/vendor", _index); FILE *f = fopen(path, "r"); if (f) { if (fscanf(f, "%hx", &_vendor_id) < 1) { @@ -295,7 +295,7 @@ open_device() { } fclose(f); } - sprintf(path, "/sys/class/input/js%d/device/id/product", _index); + sprintf(path, "/sys/class/input/js%zd/device/id/product", _index); f = fopen(path, "r"); if (f) { if (fscanf(f, "%hx", &_product_id) < 1) { @@ -304,7 +304,7 @@ open_device() { fclose(f); } char buffer[256]; - sprintf(path, "/sys/class/input/js%d/device/device/../product", _index); + sprintf(path, "/sys/class/input/js%zd/device/device/../product", _index); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -315,7 +315,7 @@ open_device() { } fclose(f); } - sprintf(path, "/sys/class/input/js%d/device/device/../manufacturer", _index); + sprintf(path, "/sys/class/input/js%zd/device/device/../manufacturer", _index); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -324,7 +324,7 @@ open_device() { } fclose(f); } - sprintf(path, "/sys/class/input/js%d/device/device/../serial", _index); + sprintf(path, "/sys/class/input/js%zd/device/device/../serial", _index); f = fopen(path, "r"); if (f) { if (fgets(buffer, sizeof(buffer), f) != nullptr) { @@ -343,8 +343,8 @@ open_device() { // are all 0, which indicates that the driver hasn't received any data for // this gamepad yet (which means it hasn't been plugged in for this session) if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) { - for (int i = 0; i < _controls.size(); ++i) { - if (_controls[i].state != 0.0) { + for (const auto &control : _controls) { + if (control.state != 0.0) { _is_connected = true; return true; } diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index fad69730c1..af0a74e3e6 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -26,7 +26,7 @@ class LinuxInputDeviceManager; */ class EXPCL_PANDA_DEVICE LinuxJoystickDevice : public InputDevice { PUBLISHED: - LinuxJoystickDevice(LinuxInputDeviceManager *manager, int index); + LinuxJoystickDevice(LinuxInputDeviceManager *manager, size_t index); virtual ~LinuxJoystickDevice(); bool check_events() const; @@ -41,7 +41,7 @@ private: LinuxInputDeviceManager *_manager; int _fd; - int _index; + size_t _index; // These are used for D-pad / hat switch emulation. int _dpad_x_axis; From d9885e4f1b7b81beedaf23159c6f09dff7bf8d00 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 6 May 2018 13:05:24 -0700 Subject: [PATCH 248/424] deploy-ng:Fix parsing bdist_apps.installers from setup.cfg --- direct/src/showutil/dist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index cd4751d33b..80f898cc54 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1028,7 +1028,10 @@ class bdist_apps(setuptools.Command): def finalize_options(self): # We need to massage the inputs a bit in case they came from a # setup.cfg file. - self.installers = _parse_dict(self.installers) + self.installers = { + key: _parse_list(value) + for key, value in _parse_dict(self.installers).items() + } def run(self): build_cmd = self.get_finalized_command('build_apps') From 0253a7a29f915d84dd03ca9b3d550dcd4e6e6e59 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 6 May 2018 13:06:29 -0700 Subject: [PATCH 249/424] deploy-ng: Remove dist.setup() This was a hack used by installation methods that did not support registering new commands correctly. The commands should now work regardless of installation method (wheel, deb, NSIS installer, etc.). --- direct/src/showutil/dist.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 80f898cc54..e4bb44448a 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1064,10 +1064,3 @@ class bdist_apps(setuptools.Command): shutil.rmtree(temp_dir) else: self.announce('\tUnknown installer: {}'.format(installer), distutils.log.ERROR) - - -def setup(**attrs): - commandClasses = attrs.setdefault("cmdclass", {}) - commandClasses['build_apps'] = build_apps - commandClasses['bdist_apps'] = bdist_apps - distutils.core.setup(**attrs) From ffe776b8f964fd353234b04ebe5f74ee0dbc5fc4 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 25 Jun 2018 18:13:56 -0700 Subject: [PATCH 250/424] deploy-ng: Support pktutil.get_data() on Python 3 Python 2 should already work. --- direct/src/showutil/dist.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index e4bb44448a..5e6e646875 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -128,7 +128,12 @@ def find_spec(fullname, path=None, target=None): spec.origin = sys.executable return spec +def get_data(path): + with open(path, 'rb') as fp: + return fp.read() + FrozenImporter.find_spec = find_spec +FrozenImporter.get_data = get_data # Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files. import os From 8c5c2c3c7dbdba3394a4a9c6ed9ed91761a29c24 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 25 Jun 2018 18:14:19 -0700 Subject: [PATCH 251/424] deploy-ng: Add package data hooks for jsonschema --- direct/src/showutil/dist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 5e6e646875..cec77c7ede 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -67,6 +67,7 @@ macosx_binary_magics = ( # mapping source directories (use / slashes) to target directories. PACKAGE_DATA_DIRS = { 'matplotlib': {'matplotlib/mpl-data': 'mpl-data'}, + 'jsonschema': {'jsonschema/schemas': 'schemas'}, } # site.py for Python 2. From dd9a7f31ec8100d0053fb5463f4288ae283e0484 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 25 Jun 2018 19:50:01 -0700 Subject: [PATCH 252/424] deploy-ng: Ensure sys.frozen is defined in frozen applications --- direct/src/showutil/dist.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index cec77c7ede..5f99c87272 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -74,6 +74,8 @@ PACKAGE_DATA_DIRS = { SITE_PY2 = u""" import sys +sys.frozen = True + # Override __import__ to set __file__ for frozen modules. prev_import = __import__ def __import__(*args, **kwargs): @@ -110,6 +112,8 @@ SITE_PY3 = u""" import sys from _frozen_importlib import _imp, FrozenImporter +sys.frozen = True + if sys.platform == 'win32': # Make sure the preferred encoding is something we actually support. import _bootlocale From ca812b890c2e06ba4fafe5dd75f4fca7403cbe37 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 25 Jun 2018 21:14:00 -0700 Subject: [PATCH 253/424] deploy-ng: Add initial support for cefpython3 --- direct/src/showutil/dist.py | 39 +++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 5f99c87272..a59e0378b7 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -8,6 +8,7 @@ import sys import subprocess import zipfile import shutil +import stat import struct import imp import string @@ -62,12 +63,25 @@ macosx_binary_magics = ( b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA', b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA') -# Some dependencies need data directories to be extracted. Key is a module -# (or package) name that triggers it to be included, the value is a dict -# mapping source directories (use / slashes) to target directories. +# Some dependencies need data directories to be extracted. This dictionary maps +# modules with data to extract. The values are lists of tuples of the form +# (source_pattern, destination_pattern, flags). The flags is a set of strings. + PACKAGE_DATA_DIRS = { - 'matplotlib': {'matplotlib/mpl-data': 'mpl-data'}, - 'jsonschema': {'jsonschema/schemas': 'schemas'}, + 'matplotlib': [('matplotlib/mpl-data/*', 'mpl-data', {})], + 'jsonschema': [('jsonschema/schemas/*', 'schemas', {})], + 'cefpython3': [ + ('cefpython3/*.pak', '', {}), + ('cefpython3/*.dat', '', {}), + ('cefpython3/*.bin', '', {}), + ('cefpython3/*.dll', '', {}), + ('cefpython3/libcef.so', '', {}), + ('cefpython3/LICENSE.txt', '', {}), + ('cefpython3/License', '', {}), + ('cefpython3/subprocess', '', {'PKG_DATA_MAKE_EXECUTABLE'}), + ('cefpython3/locals/*', 'locals', {}), + ('cefpython3/Chromium Embedded Framework.framework/Resources', 'Chromium Embedded Framework.framework/Resources', {}), + ], } # site.py for Python 2. @@ -633,9 +647,9 @@ class build_apps(setuptools.Command): shutil.copytree(sub_dir, target_dir) # Extract any other data files from dependency packages. - for module, paths in PACKAGE_DATA_DIRS.items(): + for module, datadesc in PACKAGE_DATA_DIRS.items(): if module not in freezer_modules: - pass + continue self.announce('Copying data files for module: {}'.format(module), distutils.log.INFO) @@ -643,19 +657,28 @@ class build_apps(setuptools.Command): for whl in wheelpaths: whlfile = self._get_zip_file(whl) filenames = whlfile.namelist() - for source_dir, target_dir in paths.items(): + for source_pattern, target_dir, flags in datadesc: + srcglob = p3d.GlobPattern(source_pattern) + source_dir = os.path.dirname(source_pattern) # Relocate the target dir to the build directory. target_dir = target_dir.replace('/', os.sep) target_dir = os.path.join(builddir, target_dir) for wf in filenames: if wf.lower().startswith(source_dir.lower() + '/'): + if not srcglob.matches(wf.lower()): + continue wf = wf.replace('/', os.sep) relpath = wf[len(source_dir) + 1:] source_path = os.path.join(whl, wf) target_path = os.path.join(target_dir, relpath) self.copy(source_path, target_path) + if 'PKG_DATA_MAKE_EXECUTABLE' in flags: + mode = os.stat(target_path).st_mode + mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + os.chmod(target_path, mode) + # Copy Game Files self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO) ignore_copy_list = [ From a17751adcf2f65bf4e453ad0b77ff6f1ccbc84ca Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 25 Jun 2018 21:26:05 -0700 Subject: [PATCH 254/424] deploy-ng: Avoid interactive prompt on packages fetched via a VCS --- direct/src/showutil/dist.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index a59e0378b7..79afefd26e 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -312,6 +312,13 @@ class build_apps(setuptools.Command): if int(pip_version[0]) < 9: raise RuntimeError("pip 9.0 or greater is required, but found {}".format(pip.__version__)) + # Remove any .zip files. These are built from a VCS and block for an + # interactive prompt on subsequent downloads. + if os.path.exists(whldir): + for whl in os.listdir(whldir): + if whl.endswith('.zip'): + os.remove(os.path.join(whldir, whl)) + pip_args = [ 'download', '-d', whldir, From 7a0004172a8b4e544265c422566279c6e88d0d2b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 26 Jun 2018 20:26:30 -0700 Subject: [PATCH 255/424] deploy-ng: Fix cefpython3 on Windows --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 79afefd26e..9fcbce94be 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -78,7 +78,7 @@ PACKAGE_DATA_DIRS = { ('cefpython3/libcef.so', '', {}), ('cefpython3/LICENSE.txt', '', {}), ('cefpython3/License', '', {}), - ('cefpython3/subprocess', '', {'PKG_DATA_MAKE_EXECUTABLE'}), + ('cefpython3/subprocess*', '', {'PKG_DATA_MAKE_EXECUTABLE'}), ('cefpython3/locals/*', 'locals', {}), ('cefpython3/Chromium Embedded Framework.framework/Resources', 'Chromium Embedded Framework.framework/Resources', {}), ], From f3e87a0d01324776edc1f7d4a80c43e8c5adb603 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 15 Jul 2018 11:34:36 -0700 Subject: [PATCH 256/424] deploy-ng: Create zip installers without first creating a temporary directory --- direct/src/showutil/dist.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 9fcbce94be..55ec22504d 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1073,6 +1073,26 @@ class bdist_apps(setuptools.Command): for key, value in _parse_dict(self.installers).items() } + def _get_archive_basedir(self): + return self.distribution.get_name() + + def create_zip(self, basename, build_dir): + import zipfile + + base_dir = self._get_archive_basedir() + + with zipfile.ZipFile(basename+'.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zf: + zf.write(build_dir, base_dir) + + for dirpath, dirnames, filenames in os.walk(build_dir): + for name in sorted(dirnames): + path = os.path.normpath(os.path.join(dirpath, name)) + zf.write(path, path.replace(build_dir, base_dir, 1)) + for name in filenames: + path = os.path.normpath(os.path.join(dirpath, name)) + if os.path.isfile(path): + zf.write(path, path.replace(build_dir, base_dir, 1)) + def run(self): build_cmd = self.get_finalized_command('build_apps') if not build_cmd.platforms: @@ -1092,7 +1112,9 @@ class bdist_apps(setuptools.Command): for installer in installers: self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO) - if installer in ('zip', 'gztar', 'bztar', 'xztar'): + if installer == 'zip': + self.create_zip(basename, build_dir) + elif installer in ('gztar', 'bztar', 'xztar'): base_dir = self.distribution.get_name() temp_dir = os.path.join(build_base, base_dir) if (os.path.exists(temp_dir)): From 0fd1250142b6c84aa40662eebb6d7ed2cae528ed Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 15 Jul 2018 11:59:39 -0700 Subject: [PATCH 257/424] deploy-ng: Fix execute permission bits for tarball installers created on Windows --- direct/src/showutil/dist.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 55ec22504d..bc6a214653 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1093,6 +1093,23 @@ class bdist_apps(setuptools.Command): if os.path.isfile(path): zf.write(path, path.replace(build_dir, base_dir, 1)) + def create_tarball(self, basename, build_dir, tar_compression): + import tarfile + + base_dir = self._get_archive_basedir() + build_cmd = self.get_finalized_command('build_apps') + binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys()) + + def tarfilter(tarinfo): + if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names: + tarinfo.mode = 0o755 + else: + tarinfo.mode = 0o644 + return tarinfo + + with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf: + tf.add(build_dir, base_dir, filter=tarfilter) + def run(self): build_cmd = self.get_finalized_command('build_apps') if not build_cmd.platforms: @@ -1115,14 +1132,10 @@ class bdist_apps(setuptools.Command): if installer == 'zip': self.create_zip(basename, build_dir) elif installer in ('gztar', 'bztar', 'xztar'): - base_dir = self.distribution.get_name() - temp_dir = os.path.join(build_base, base_dir) - if (os.path.exists(temp_dir)): - shutil.rmtree(temp_dir) - shutil.copytree(build_dir, temp_dir) + compress = installer.replace('tar', '') + if compress == 'bz': + compress = 'bz2' - shutil.make_archive(basename, installer, root_dir=build_base, base_dir=base_dir) - - shutil.rmtree(temp_dir) + self.create_tarball(basename, build_dir, compress) else: self.announce('\tUnknown installer: {}'.format(installer), distutils.log.ERROR) From 53dea28f53e194597e27801fae8b7cb29c871daa Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 18 Aug 2018 21:31:12 -0700 Subject: [PATCH 258/424] deploy-ng: Remove docstrings from frozen modules --- direct/src/showutil/FreezeTool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 978891c0f1..a60b1efb89 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1763,7 +1763,7 @@ class Freezer: code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext) else: code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) - code = compile(code, moduleName, 'exec') + code = compile(code, moduleName, 'exec', optimize=2) code = marshal.dumps(code) moduleList.append((moduleName, len(pool), len(code))) pool += code From 495ce50d84b281eb01dba5d31f163dd21f4352fe Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 18 Aug 2018 21:36:45 -0700 Subject: [PATCH 259/424] deploy-ng: NULL terminate readlink() result in deploy-stub --- pandatool/src/deploy-stub/deploy-stub.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index cf44de992e..0acf31085a 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -545,6 +545,7 @@ static void *map_blob(off_t offset, size_t size) { perror("readlink(/proc/self/exe)"); return NULL; } + buffer[pathlen] = '\0'; runtime = fopen(buffer, "rb"); #endif From 2486b12a142927f29000a901815d35ebf8c376d6 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 19 Aug 2018 14:31:29 -0700 Subject: [PATCH 260/424] deploy-ng: Support generating a requirements.txt file via pipenv --- direct/src/showutil/dist.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index bc6a214653..7ea886d1fc 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -264,6 +264,16 @@ class build_apps(setuptools.Command): elif num_gui_apps == 1: self.macos_main_app = list(self.gui_apps.keys())[0] + use_pipenv = ( + 'Pipfile' in os.path.basename(self.requirements_path) or + not os.path.exists(self.requirements_path) and os.path.exists('Pipfile') + ) + if use_pipenv: + reqspath = os.path.join(self.build_base, 'requirements.txt') + with open(reqspath, 'w') as reqsfile: + subprocess.check_call(['pipenv', 'lock', '--requirements'], stdout=reqsfile) + self.requirements_path = reqspath + assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps' From 8b927318cbbd9934cd42b509447ecc3b8fcaffa1 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 20 Aug 2018 14:31:04 +0200 Subject: [PATCH 261/424] makewheel: on manylinux, should include all lib dependencies (Except for those explicitly whitelisted in PEP 513) --- makepanda/makewheel.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index b2019732df..4931dc1094 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -94,6 +94,19 @@ EXCLUDE_EXT = [".pyc", ".pyo", ".N", ".prebuilt", ".xcf", ".plist", ".vcproj", " # Plug-ins to install. PLUGIN_LIBS = ["pandagl", "pandagles", "pandagles2", "pandadx9", "p3tinydisplay", "p3ptloader", "p3assimp", "p3ffmpeg", "p3openal_audio", "p3fmod_audio"] +# Libraries included in manylinux ABI that should be ignored. See PEP 513/571. +MANYLINUX_LIBS = [ + "libgcc_s.so.1", "libstdc++.so.6", "libm.so.6", "libdl.so.2", "librt.so.1", + "libcrypt.so.1", "libc.so.6", "libnsl.so.1", "libutil.so.1", + "libpthread.so.0", "libresolv.so.2", "libX11.so.6", "libXext.so.6", + "libXrender.so.1", "libICE.so.6", "libSM.so.6", "libGL.so.1", + "libgobject-2.0.so.0", "libgthread-2.0.so.0", "libglib-2.0.so.0", + + # These are not mentioned in manylinux1 spec but should nonetheless always + # be excluded. + "linux-vdso.so.1", "linux-gate.so.1", "ld-linux.so.2", +] + WHEEL_DATA = """Wheel-Version: 1.0 Generator: makepanda Root-Is-Purelib: false @@ -263,6 +276,7 @@ class WheelFile(object): # Used to locate dependency libraries. self.lib_path = [] self.dep_paths = {} + self.ignore_deps = set() def consider_add_dependency(self, target_path, dep, search_path=None): """Considers adding a dependency library. @@ -275,8 +289,10 @@ class WheelFile(object): self.dep_paths[dep] = None - if dep.lower().startswith("python") or os.path.basename(dep).startswith("libpython"): - # Don't include the Python library. + if dep in self.ignore_deps or dep.lower().startswith("python") or os.path.basename(dep).startswith("libpython"): + # Don't include the Python library, or any other explicit ignore. + if GetVerbose(): + print("Ignoring {0} (explicitly ignored)".format(dep)) return if sys.platform == "darwin" and dep.endswith(".so"): @@ -297,7 +313,8 @@ class WheelFile(object): if not source_path: # Couldn't find library in the panda3d lib dir. - #print("Ignoring %s" % (dep)) + if GetVerbose(): + print("Ignoring {0} (not in search path)".format(dep)) return self.dep_paths[dep] = target_path @@ -307,6 +324,8 @@ class WheelFile(object): def write_file(self, target_path, source_path): """Adds the given file to the .whl file.""" + orig_source_path = source_path + # If this is a .so file, we should set the rpath appropriately. temp = None ext = os.path.splitext(source_path)[1] @@ -402,7 +421,7 @@ class WheelFile(object): self.records.append("{0},sha256={1},{2}\n".format(target_path, digest, size)) if GetVerbose(): - print("Adding %s from %s" % (target_path, source_path)) + print("Adding {0} from {1}".format(target_path, orig_source_path)) self.zip_file.write(source_path, target_path) #if temp: @@ -509,6 +528,18 @@ def makewheel(version, output_dir, platform=None): if sys.platform == "win32": whl.lib_path.append(join(output_dir, "python", "DLLs")) + if platform.startswith("manylinux"): + # On manylinux1, we pick up all libraries except for the ones specified + # by the manylinux1 ABI. + whl.lib_path.append("/usr/local/lib") + + if platform.endswith("_x86_64"): + whl.lib_path += ["/lib64", "/usr/lib64"] + else: + whl.lib_path += ["/lib", "/usr/lib"] + + whl.ignore_deps.update(MANYLINUX_LIBS) + # Add the trees with Python modules. whl.write_directory('direct', direct_dir) From aae2b1e5ad5ea582ee5895e136482986e0b3ae34 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 26 Aug 2018 21:18:11 +0200 Subject: [PATCH 262/424] putil: fix a compiler warning --- panda/src/putil/gamepadButton.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index fc580e6b5e..12fae57b50 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -63,7 +63,7 @@ joystick(int button_number) { if (button_number >= 0) { // "button1" does not exist, it is called "trigger" instead static pvector buttons(1, _trigger); - while (button_number >= buttons.size()) { + while ((size_t)button_number >= buttons.size()) { char numstr[20]; sprintf(numstr, "joystick%d", (int)buttons.size() + 1); ButtonHandle handle; From 7cc100c38c97ba9b27508e173aff189f73a29012 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 26 Aug 2018 21:18:29 +0200 Subject: [PATCH 263/424] device: change ControlAxis enum to an Axis enum class --- panda/src/device/evdevInputDevice.cxx | 64 ++++++++--------- panda/src/device/inputDevice.I | 39 +++-------- panda/src/device/inputDevice.cxx | 74 ++++++++++---------- panda/src/device/inputDevice.h | 70 +++++++++--------- panda/src/device/ioKitInputDevice.cxx | 50 ++++++------- panda/src/device/linuxInputDeviceManager.cxx | 8 +-- panda/src/device/linuxJoystickDevice.cxx | 50 ++++++------- panda/src/device/winRawInputDevice.cxx | 38 +++++----- panda/src/device/xInputDevice.cxx | 48 ++++++------- 9 files changed, 209 insertions(+), 232 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index dec94f803d..cf15b50f2a 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -419,89 +419,89 @@ init_device() { for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { - ControlAxis axis = C_none; + Axis axis = Axis::none; switch (i) { case ABS_X: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_x; + axis = InputDevice::Axis::left_x; } else if (_device_class == DC_flight_stick) { - axis = InputDevice::C_roll; + axis = InputDevice::Axis::roll; } else { - axis = InputDevice::C_x; + axis = InputDevice::Axis::x; } break; case ABS_Y: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_y; + axis = InputDevice::Axis::left_y; } else if (_device_class == DC_flight_stick) { - axis = InputDevice::C_pitch; + axis = InputDevice::Axis::pitch; } else { - axis = InputDevice::C_y; + axis = InputDevice::Axis::y; } break; case ABS_Z: if (quirks & QB_rstick_from_z) { - axis = InputDevice::C_right_x; + axis = InputDevice::Axis::right_x; } else if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_trigger; + axis = InputDevice::Axis::left_trigger; have_analog_triggers = true; } else if (_device_class == DC_3d_mouse) { - axis = InputDevice::C_z; + axis = InputDevice::Axis::z; } else { - axis = InputDevice::C_throttle; + axis = InputDevice::Axis::throttle; } break; case ABS_RX: if (_device_class == DC_3d_mouse) { - axis = InputDevice::C_pitch; + axis = InputDevice::Axis::pitch; } else if ((quirks & QB_rstick_from_z) == 0) { - axis = InputDevice::C_right_x; + axis = InputDevice::Axis::right_x; } break; case ABS_RY: if (_device_class == DC_3d_mouse) { - axis = InputDevice::C_roll; + axis = InputDevice::Axis::roll; } else if ((quirks & QB_rstick_from_z) == 0) { - axis = InputDevice::C_right_y; + axis = InputDevice::Axis::right_y; } break; case ABS_RZ: if (quirks & QB_rstick_from_z) { - axis = InputDevice::C_right_y; + axis = InputDevice::Axis::right_y; } else if (_device_class == DC_gamepad) { - axis = InputDevice::C_right_trigger; + axis = InputDevice::Axis::right_trigger; have_analog_triggers = true; } else { - axis = InputDevice::C_yaw; + axis = InputDevice::Axis::yaw; } break; case ABS_THROTTLE: if (quirks & QB_rudder_from_throttle) { - axis = InputDevice::C_rudder; + axis = InputDevice::Axis::rudder; } else { - axis = InputDevice::C_throttle; + axis = InputDevice::Axis::throttle; } break; case ABS_RUDDER: - axis = InputDevice::C_rudder; + axis = InputDevice::Axis::rudder; break; case ABS_WHEEL: - axis = InputDevice::C_wheel; + axis = InputDevice::Axis::wheel; break; case ABS_GAS: if (_device_class == DC_gamepad) { - axis = InputDevice::C_right_trigger; + axis = InputDevice::Axis::right_trigger; have_analog_triggers = true; } else { - axis = InputDevice::C_accelerator; + axis = InputDevice::Axis::accelerator; } break; case ABS_BRAKE: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_trigger; + axis = InputDevice::Axis::left_trigger; have_analog_triggers = true; } else { - axis = InputDevice::C_brake; + axis = InputDevice::Axis::brake; } break; case ABS_HAT0X: @@ -539,12 +539,12 @@ init_device() { // We'd like to reverse the Y axis to match the XInput behavior. // Also reverse the yaw axis to match right-hand coordinate system. // Also T.Flight Hotas X throttle is reversed and can go backwards. - if (axis == C_yaw || axis == C_rudder || axis == C_left_y || axis == C_right_y || - (axis == C_throttle && (quirks & QB_reversed_throttle) != 0) || - (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z || axis == C_roll))) { + if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || + (axis == Axis::throttle && (quirks & QB_reversed_throttle) != 0) || + (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { std::swap(absinfo.maximum, absinfo.minimum); } - if (axis == C_throttle && (quirks & QB_centered_throttle) != 0) { + if (axis == Axis::throttle && (quirks & QB_centered_throttle) != 0) { index = add_control(axis, absinfo.maximum, absinfo.minimum, true); } else { index = add_control(axis, absinfo.minimum, absinfo.maximum); @@ -584,8 +584,8 @@ init_device() { if (_ltrigger_code >= 0 && _rtrigger_code >= 0 && !have_analog_triggers) { // Emulate analog triggers. _ltrigger_control = (int)_controls.size(); - add_control(C_left_trigger, 0, 1, false); - add_control(C_right_trigger, 0, 1, false); + add_control(Axis::left_trigger, 0, 1, false); + add_control(Axis::right_trigger, 0, 1, false); } else { _ltrigger_code = -1; _rtrigger_code = -1; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index 43c71fd1eb..a8eb5a127b 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -292,14 +292,14 @@ get_num_controls() const { } /** - * Associates the indicated ControlAxis with the control of the indicated index - * number. Pass C_none to turn off any association. + * Associates the indicated Axis with the control of the indicated index + * number. Pass Axis::none to turn off any association. * * It is not necessary to call this if you simply want to query the state of * the various controls by index number. */ INLINE void InputDevice:: -set_control_map(size_t index, InputDevice::ControlAxis axis) { +set_control_map(size_t index, InputDevice::Axis axis) { LightMutexHolder holder(_lock); if (index >= _controls.size()) { _controls.resize(index + 1, AnalogState()); @@ -309,16 +309,16 @@ set_control_map(size_t index, InputDevice::ControlAxis axis) { } /** - * Returns the ControlAxis that was previously associated with the given index - * number by a call to set_control_map(), or C_none if no control was + * Returns the Axis that was previously associated with the given index + * number by a call to set_control_map(), or Axis::none if no control was * associated. */ -INLINE InputDevice::ControlAxis InputDevice:: +INLINE InputDevice::Axis InputDevice:: get_control_map(size_t index) const { if (index < _controls.size()) { return _controls[index].axis; } else { - return C_none; + return Axis::none; } } @@ -356,7 +356,7 @@ get_control(size_t index) const { * if the axis was not found in the list. */ INLINE InputDevice::AnalogState InputDevice:: -find_control(InputDevice::ControlAxis axis) const { +find_control(InputDevice::Axis axis) const { for (size_t i = 0; i < _controls.size(); ++i) { if (_controls[i].axis == axis) { return _controls[i]; @@ -444,16 +444,6 @@ operator < (const InputDevice &) const { return false; } -/** - * - */ -INLINE InputDevice::ButtonState:: -ButtonState() : - handle(ButtonHandle::none()), - state(S_unknown) -{ -} - /** * */ @@ -463,16 +453,3 @@ ButtonState(ButtonHandle handle) : state(S_unknown) { } - -/** - * - */ -INLINE InputDevice::AnalogState:: -AnalogState() : - axis(C_none), - state(0.0), - known(false), - _scale(1.0), - _bias(0.0) -{ -} diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 00e3e0056c..e24cd9a0df 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -124,7 +124,7 @@ get_pointer_events() { * Called by the implementation to add a new known control. */ int InputDevice:: -add_control(ControlAxis axis, int minimum, int maximum, bool centered) { +add_control(Axis axis, int minimum, int maximum, bool centered) { AnalogState state; state.axis = axis; if (centered) { @@ -146,20 +146,20 @@ add_control(ControlAxis axis, int minimum, int maximum, bool centered) { * tries to guess whether the control is centered or not. */ int InputDevice:: -add_control(ControlAxis axis, int minimum, int maximum) { +add_control(Axis axis, int minimum, int maximum) { bool centered = (minimum < 0) - || axis == C_x - || axis == C_y - || axis == C_z - || axis == C_yaw - || axis == C_pitch - || axis == C_roll - || axis == C_left_x - || axis == C_left_y - || axis == C_right_x - || axis == C_right_y - || axis == C_wheel - || axis == C_rudder; + || axis == Axis::x + || axis == Axis::y + || axis == Axis::z + || axis == Axis::yaw + || axis == Axis::pitch + || axis == Axis::roll + || axis == Axis::left_x + || axis == Axis::left_y + || axis == Axis::right_x + || axis == Axis::right_y + || axis == Axis::wheel + || axis == Axis::rudder; return add_control(axis, minimum, maximum, centered); } @@ -287,7 +287,7 @@ set_control_state(int index, double state) { device_cat.spam() << "Changed control " << index; - if (_controls[index].axis != C_none) { + if (_controls[index].axis != Axis::none) { device_cat.spam(false) << " (" << _controls[index].axis << ")"; } @@ -315,7 +315,7 @@ control_changed(int index, int state) { device_cat.spam() << "Changed control " << index; - if (_controls[index].axis != C_none) { + if (_controls[index].axis != Axis::none) { device_cat.spam(false) << " (" << _controls[index].axis << ")"; } @@ -556,63 +556,63 @@ format_device_class(DeviceClass dc) { * Returns a string describing the given axis enumerant. */ std::string InputDevice:: -format_axis(ControlAxis axis) { +format_axis(Axis axis) { switch (axis) { - case InputDevice::C_none: + case InputDevice::Axis::none: return "none"; - case InputDevice::C_x: + case InputDevice::Axis::x: return "x"; - case InputDevice::C_y: + case InputDevice::Axis::y: return "y"; - case InputDevice::C_z: + case InputDevice::Axis::z: return "z"; - case InputDevice::C_yaw: + case InputDevice::Axis::yaw: return "yaw"; - case InputDevice::C_pitch: + case InputDevice::Axis::pitch: return "pitch"; - case InputDevice::C_roll: + case InputDevice::Axis::roll: return "roll"; - case InputDevice::C_left_x: + case InputDevice::Axis::left_x: return "left_x"; - case InputDevice::C_left_y: + case InputDevice::Axis::left_y: return "left_y"; - case InputDevice::C_left_trigger: + case InputDevice::Axis::left_trigger: return "left_trigger"; - case InputDevice::C_right_x: + case InputDevice::Axis::right_x: return "right_x"; - case InputDevice::C_right_y: + case InputDevice::Axis::right_y: return "right_y"; - case InputDevice::C_right_trigger: + case InputDevice::Axis::right_trigger: return "right_trigger"; - //case InputDevice::C_trigger: + //case InputDevice::Axis::trigger: // return "trigger"; - case InputDevice::C_throttle: + case InputDevice::Axis::throttle: return "throttle"; - case InputDevice::C_rudder: + case InputDevice::Axis::rudder: return "rudder"; - case InputDevice::C_wheel: + case InputDevice::Axis::wheel: return "wheel"; - case InputDevice::C_accelerator: + case InputDevice::Axis::accelerator: return "accelerator"; - case InputDevice::C_brake: + case InputDevice::Axis::brake: return "brake"; } return "**invalid**"; @@ -625,7 +625,7 @@ operator << (std::ostream &out, InputDevice::DeviceClass dc) { } std::ostream & -operator << (std::ostream &out, InputDevice::ControlAxis axis) { +operator << (std::ostream &out, InputDevice::Axis axis) { out << InputDevice::format_axis(axis); return out; } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index fd113a0852..77b42b43c0 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -95,35 +95,35 @@ public: ~InputDevice(); PUBLISHED: - enum ControlAxis { - C_none, + enum class Axis { + none, // Generic translational axes - C_x, - C_y, - C_z, + x, + y, + z, // Generic rotational axes, used by joysticks and 3D mice - C_yaw, - C_pitch, - C_roll, + yaw, + pitch, + roll, // Gamepad - C_left_x, - C_left_y, - C_left_trigger, - C_right_x, - C_right_y, - C_right_trigger, + left_x, + left_y, + left_trigger, + right_x, + right_y, + right_trigger, // Flight stick specific - C_throttle, - C_rudder, // When available separately from yaw + throttle, + rudder, // When available separately from yaw // Steering wheel / pedals - C_wheel, - C_accelerator, - C_brake, + wheel, + accelerator, + brake, }; INLINE std::string get_name() const; @@ -172,8 +172,8 @@ PUBLISHED: INLINE bool is_button_known(size_t index) const; INLINE size_t get_num_controls() const; - INLINE void set_control_map(size_t index, ControlAxis axis); - INLINE ControlAxis get_control_map(size_t index) const; + INLINE void set_control_map(size_t index, Axis axis); + INLINE Axis get_control_map(size_t index) const; INLINE double get_control_state(size_t index) const; INLINE bool is_control_known(size_t index) const; @@ -191,12 +191,12 @@ PUBLISHED: virtual void output(std::ostream &out) const; static std::string format_device_class(DeviceClass dc); - static std::string format_axis(ControlAxis axis); + static std::string format_axis(Axis axis); protected: // Called during the constructor to add new controls or buttons - int add_control(ControlAxis axis, int minimum, int maximum, bool centered); - int add_control(ControlAxis axis, int minimum, int maximum); + int add_control(Axis axis, int minimum, int maximum, bool centered); + int add_control(Axis axis, int minimum, int maximum); void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); @@ -271,28 +271,28 @@ PUBLISHED: class ButtonState { public: - INLINE ButtonState(); + constexpr ButtonState() = default; INLINE ButtonState(ButtonHandle handle); PUBLISHED: - ButtonHandle handle; - State state; + ButtonHandle handle = ButtonHandle::none(); + State state = S_unknown; }; typedef pvector Buttons; Buttons _buttons; class AnalogState { public: - INLINE AnalogState(); + constexpr AnalogState() = default; PUBLISHED: - ControlAxis axis; - double state; - bool known; + Axis axis = Axis::none; + double state = 0.0; + bool known = false; public: - double _scale; - double _bias; + double _scale = 1.0; + double _bias = 0.0; }; typedef pvector Controls; Controls _controls; @@ -307,7 +307,7 @@ PUBLISHED: INLINE ButtonState find_button(ButtonHandle handle) const; INLINE AnalogState get_control(size_t index) const; - INLINE AnalogState find_control(ControlAxis axis) const; + INLINE AnalogState find_control(Axis axis) const; // Make device buttons and controls iterable MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button); @@ -337,7 +337,7 @@ INLINE std::ostream &operator << (std::ostream &out, const InputDevice &device) } EXPCL_PANDA_DEVICE std::ostream &operator << (std::ostream &out, InputDevice::DeviceClass dc); -EXPCL_PANDA_DEVICE std::ostream &operator << (std::ostream &out, InputDevice::ControlAxis axis); +EXPCL_PANDA_DEVICE std::ostream &operator << (std::ostream &out, InputDevice::Axis axis); #include "inputDevice.I" diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index 5854fc3015..3313e3f78a 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -172,7 +172,7 @@ on_remove() { void IOKitInputDevice:: parse_element(IOHIDElementRef element) { ButtonHandle handle = ButtonHandle::none(); - ControlAxis axis = C_none; + Axis axis = Axis::none; uint32_t page = IOHIDElementGetUsagePage(element); uint32_t usage = IOHIDElementGetUsage(element); @@ -183,60 +183,60 @@ parse_element(IOHIDElementRef element) { switch (usage) { case kHIDUsage_GD_X: if (_device_class == DC_gamepad) { - axis = C_left_x; + axis = Axis::left_x; } else if (_device_class == DC_flight_stick) { - axis = C_roll; + axis = Axis::roll; } else if (_device_class == DC_mouse) { _pointer_x = element; return; } else { - axis = C_x; + axis = Axis::x; } break; case kHIDUsage_GD_Y: if (_device_class == DC_gamepad) { - axis = C_left_y; + axis = Axis::left_y; } else if (_device_class == DC_flight_stick) { - axis = C_pitch; + axis = Axis::pitch; } else if (_device_class == DC_mouse) { _pointer_y = element; return; } else { - axis = C_y; + axis = Axis::y; } break; case kHIDUsage_GD_Z: if (_device_class == DC_gamepad) { - axis = C_left_trigger; + axis = Axis::left_trigger; } else if (_device_class == DC_flight_stick) { - axis = C_throttle; + axis = Axis::throttle; } else { - axis = C_z; + axis = Axis::z; } break; case kHIDUsage_GD_Rx: if (_device_class == DC_gamepad) { - axis = C_right_x; + axis = Axis::right_x; } else { - axis = C_pitch; + axis = Axis::pitch; } break; case kHIDUsage_GD_Ry: if (_device_class == DC_gamepad) { - axis = C_right_y; + axis = Axis::right_y; } else { - axis = C_roll; + axis = Axis::roll; } break; case kHIDUsage_GD_Rz: if (_device_class == DC_gamepad) { - axis = C_right_trigger; + axis = Axis::right_trigger; } else { - axis = C_yaw; + axis = Axis::yaw; } break; case kHIDUsage_GD_Slider: - axis = C_rudder; + axis = Axis::rudder; break; case kHIDUsage_GD_Dial: break; @@ -268,28 +268,28 @@ parse_element(IOHIDElementRef element) { case kHIDPage_Simulation: switch (usage) { case kHIDUsage_Sim_Rudder: - axis = C_rudder; + axis = Axis::rudder; break; case kHIDUsage_Sim_Throttle: - axis = C_throttle; + axis = Axis::throttle; break; case kHIDUsage_Sim_Accelerator: - axis = C_accelerator; + axis = Axis::accelerator; break; case kHIDUsage_Sim_Brake: - axis = C_brake; + axis = Axis::brake; break; } break; } - if (axis != C_none) { + if (axis != Axis::none) { int min = IOHIDElementGetLogicalMin(element); int max = IOHIDElementGetLogicalMax(element); - if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { + if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. add_control(axis, max, min, true); - } else if (axis == C_yaw || axis == C_rudder || axis == C_left_y || axis == C_right_y || - (_device_class == DC_3d_mouse && (axis == C_y || axis == C_z || axis == C_roll))) { + } else if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || + (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { // We'd like to reverse the Y axis to match the XInput behavior. // We also reverse yaw to obey the right-hand rule. add_control(axis, max, min); diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 2e956cb739..ce7ce558e2 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -118,10 +118,10 @@ consider_add_evdev_device(size_t ev_index) { _evdev_devices[ev_index] = device; if (device->is_connected()) { - _connected_devices.add_device(MOVE(device)); + _connected_devices.add_device(std::move(device)); } else { // Wait for activity on the device before it is considered connected. - _inactive_devices.add_device(MOVE(device)); + _inactive_devices.add_device(std::move(device)); } return _evdev_devices[ev_index]; } @@ -189,10 +189,10 @@ consider_add_js_device(size_t js_index) { InputDevice *device_p = device.p(); if (device->is_connected()) { - _connected_devices.add_device(MOVE(device)); + _connected_devices.add_device(std::move(device)); } else { // Wait for activity on the device before it is considered connected. - _inactive_devices.add_device(MOVE(device)); + _inactive_devices.add_device(std::move(device)); } return device_p; } diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 6617fc1dcd..2c10ede51c 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -152,71 +152,71 @@ open_device() { ioctl(_fd, JSIOCGAXMAP, axmap); for (uint8_t i = 0; i < num_axes; ++i) { - ControlAxis axis = C_none; + Axis axis = Axis::none; switch (axmap[i]) { case ABS_X: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_x; + axis = InputDevice::Axis::left_x; } else if (_device_class == DC_flight_stick) { - axis = InputDevice::C_roll; + axis = InputDevice::Axis::roll; } else { - axis = InputDevice::C_x; + axis = InputDevice::Axis::x; } break; case ABS_Y: if (_device_class == DC_gamepad) { - axis = InputDevice::C_left_y; + axis = InputDevice::Axis::left_y; } else if (_device_class == DC_flight_stick) { - axis = InputDevice::C_pitch; + axis = InputDevice::Axis::pitch; } else { - axis = InputDevice::C_y; + axis = InputDevice::Axis::y; } break; case ABS_Z: if (_device_class == DC_gamepad) { - axis = C_left_trigger; + axis = Axis::left_trigger; } else { - //axis = C_trigger; + //axis = Axis::trigger; } break; case ABS_RX: - axis = C_right_x; + axis = Axis::right_x; break; case ABS_RY: - axis = C_right_y; + axis = Axis::right_y; break; case ABS_RZ: if (_device_class == DC_gamepad) { - axis = InputDevice::C_right_trigger; + axis = InputDevice::Axis::right_trigger; } else { - axis = InputDevice::C_yaw; + axis = InputDevice::Axis::yaw; } break; case ABS_THROTTLE: - axis = InputDevice::C_throttle; + axis = InputDevice::Axis::throttle; break; case ABS_RUDDER: - axis = InputDevice::C_rudder; + axis = InputDevice::Axis::rudder; break; case ABS_WHEEL: - axis = InputDevice::C_wheel; + axis = InputDevice::Axis::wheel; break; case ABS_GAS: - axis = InputDevice::C_accelerator; + axis = InputDevice::Axis::accelerator; break; case ABS_BRAKE: - axis = InputDevice::C_brake; + axis = InputDevice::Axis::brake; break; case ABS_HAT0X: @@ -231,7 +231,7 @@ open_device() { _buttons.push_back(ButtonState(GamepadButton::hat_left())); _buttons.push_back(ButtonState(GamepadButton::hat_right())); } - axis = C_none; + axis = Axis::none; } break; @@ -247,7 +247,7 @@ open_device() { _buttons.push_back(ButtonState(GamepadButton::hat_up())); _buttons.push_back(ButtonState(GamepadButton::hat_down())); } - axis = C_none; + axis = Axis::none; } break; @@ -256,17 +256,17 @@ open_device() { device_cat.debug() << "Unmapped /dev/input/js" << _index << " axis " << (int)i << ": 0x" << std::hex << (int)axmap[i] << "\n"; } - axis = C_none; + axis = Axis::none; break; } _controls[i].axis = axis; - if (axis == C_left_trigger || axis == C_right_trigger) { + if (axis == Axis::left_trigger || axis == Axis::right_trigger) { // We'd like to use 0.0 to indicate the resting position. _controls[i]._scale = 1.0 / 65534.0; _controls[i]._bias = 0.5; have_analog_triggers = true; - } else if (axis == C_left_y || axis == C_right_y || axis == C_y) { + } else if (axis == Axis::left_y || axis == Axis::right_y || axis == Axis::y) { _controls[i]._scale = 1.0 / -32767.0; _controls[i]._bias = 0.0; } else { @@ -279,8 +279,8 @@ open_device() { if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) { // Emulate analog triggers. _ltrigger_control = (int)_controls.size(); - add_control(C_left_trigger, 0, 1, false); - add_control(C_right_trigger, 0, 1, false); + add_control(Axis::left_trigger, 0, 1, false); + add_control(Axis::right_trigger, 0, 1, false); } else { _ltrigger_button = -1; _rtrigger_button = -1; diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index f4603c5aa8..db8f82d76d 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -478,71 +478,71 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { is_signed = false; } - ControlAxis axis = C_none; + Axis axis = Axis::none; switch (cap.UsagePage) { case HID_USAGE_PAGE_GENERIC: switch (usage) { case HID_USAGE_GENERIC_X: if (_device_class == DC_gamepad) { - axis = C_left_x; + axis = Axis::left_x; } else if (_device_class == DC_flight_stick) { - axis = C_roll; + axis = Axis::roll; } else { - axis = C_x; + axis = Axis::x; } break; case HID_USAGE_GENERIC_Y: if (_device_class == DC_gamepad) { - axis = C_left_y; + axis = Axis::left_y; swap(cap.LogicalMin, cap.LogicalMax); } else if (_device_class == DC_flight_stick) { - axis = C_pitch; + axis = Axis::pitch; } else { - axis = C_y; + axis = Axis::y; swap(cap.LogicalMin, cap.LogicalMax); } break; case HID_USAGE_GENERIC_Z: if (_device_class == DC_gamepad) { - axis = C_left_trigger; + axis = Axis::left_trigger; } else if (_device_class == DC_flight_stick) { - axis = C_throttle; + axis = Axis::throttle; } else { - axis = C_z; + axis = Axis::z; swap(cap.LogicalMin, cap.LogicalMax); } break; case HID_USAGE_GENERIC_RX: if (_device_class == DC_gamepad) { - axis = C_right_x; + axis = Axis::right_x; } else { - axis = C_pitch; + axis = Axis::pitch; } break; case HID_USAGE_GENERIC_RY: if (_device_class == DC_gamepad) { - axis = C_right_y; + axis = Axis::right_y; } else { - axis = C_roll; + axis = Axis::roll; } swap(cap.LogicalMin, cap.LogicalMax); break; case HID_USAGE_GENERIC_RZ: if (_device_class == DC_gamepad) { - axis = C_right_trigger; + axis = Axis::right_trigger; } else { // Flip to match Panda's convention for heading. - axis = C_yaw; + axis = Axis::yaw; swap(cap.LogicalMin, cap.LogicalMax); } break; case HID_USAGE_GENERIC_SLIDER: // Flip to match Panda's convention for heading. - axis = C_rudder; + axis = Axis::rudder; swap(cap.LogicalMin, cap.LogicalMax); break; case HID_USAGE_GENERIC_WHEEL: - axis = C_wheel; + axis = Axis::wheel; break; case HID_USAGE_GENERIC_HATSWITCH: // This is handled specially. @@ -554,7 +554,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { } int control_index; - if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == C_throttle) { + if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. control_index = add_control(axis, cap.LogicalMax, cap.LogicalMin, true); } else if (!is_signed) { diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index f2f0b45b85..7e1ddea132 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -315,42 +315,42 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { default: case XINPUT_DEVSUBTYPE_GAMEPAD: _device_class = DC_gamepad; - set_control_map(0, C_left_trigger); - set_control_map(1, C_right_trigger); - set_control_map(2, C_left_x); - set_control_map(3, C_left_y); - set_control_map(4, C_right_x); - set_control_map(5, C_right_y); + set_control_map(0, Axis::left_trigger); + set_control_map(1, Axis::right_trigger); + set_control_map(2, Axis::left_x); + set_control_map(3, Axis::left_y); + set_control_map(4, Axis::right_x); + set_control_map(5, Axis::right_y); break; case XINPUT_DEVSUBTYPE_WHEEL: _device_class = DC_steering_wheel; - set_control_map(0, C_brake); - set_control_map(1, C_accelerator); - set_control_map(2, C_wheel); - set_control_map(3, C_none); - set_control_map(4, C_none); - set_control_map(5, C_none); + set_control_map(0, Axis::brake); + set_control_map(1, Axis::accelerator); + set_control_map(2, Axis::wheel); + set_control_map(3, Axis::none); + set_control_map(4, Axis::none); + set_control_map(5, Axis::none); break; case XINPUT_DEVSUBTYPE_FLIGHT_STICK: _device_class = DC_flight_stick; - set_control_map(0, C_yaw); - set_control_map(1, C_throttle); - set_control_map(2, C_roll); - set_control_map(3, C_pitch); - set_control_map(4, C_none); - set_control_map(5, C_none); + set_control_map(0, Axis::yaw); + set_control_map(1, Axis::throttle); + set_control_map(2, Axis::roll); + set_control_map(3, Axis::pitch); + set_control_map(4, Axis::none); + set_control_map(5, Axis::none); break; case XINPUT_DEVSUBTYPE_DANCE_PAD: _device_class = DC_dance_pad; - set_control_map(0, C_none); - set_control_map(1, C_none); - set_control_map(2, C_none); - set_control_map(3, C_none); - set_control_map(4, C_none); - set_control_map(5, C_none); + set_control_map(0, Axis::none); + set_control_map(1, Axis::none); + set_control_map(2, Axis::none); + set_control_map(3, Axis::none); + set_control_map(4, Axis::none); + set_control_map(5, Axis::none); break; } From 1983382b3be095eaaf7c228a9f03a9235d812484 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Fri, 7 Sep 2018 19:57:14 -0700 Subject: [PATCH 264/424] deploy-ng: Improve file_handler API User-defined file handlers are added to a dictionary of default handlers (just has egg2bam for *.egg files for now). Any user-provided file handlers for a given file type will overwrite default file handlers. This allows users to define file handlers without having to re-add egg2bam to the list. --- direct/src/showutil/dist.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 7ea886d1fc..f4d8a70f2c 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -179,6 +179,9 @@ class build_apps(setuptools.Command): ('build-base=', None, 'directory to build applications in'), ('requirements-path=', None, 'path to requirements.txt file for pip'), ] + default_file_handlers = { + '.egg': egg2bam, + } def initialize_options(self): self.build_base = os.path.join(os.getcwd(), 'build') @@ -200,9 +203,7 @@ class build_apps(setuptools.Command): self.log_append = False self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] - self.file_handlers= { - '.egg': egg2bam, - } + self.file_handlers = {} self.exclude_dependencies = [ # Windows 'kernel32.dll', 'user32.dll', 'wsock32.dll', 'ws2_32.dll', @@ -277,6 +278,10 @@ class build_apps(setuptools.Command): assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps' + tmp = self.default_file_handlers.copy() + tmp.update(self.file_handlers) + self.file_handlers = tmp + def run(self): if not self.platforms: platforms = [p3d.PandaSystem.get_platform()] From 9dd29d7ac0a761a4aa322f56330799abc8bb4d7b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Fri, 7 Sep 2018 19:59:14 -0700 Subject: [PATCH 265/424] deploy-ng: Improve error reporting for egg2bam --- direct/src/showutil/dist.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index f4d8a70f2c..1705b5fefd 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -49,12 +49,17 @@ def _parse_dict(input): def egg2bam(_build_cmd, srcpath, dstpath): dstpath = dstpath + '.bam' - subprocess.call([ - 'egg2bam', - '-o', - dstpath, - srcpath - ]) + try: + subprocess.check_call([ + 'egg2bam', + '-o', + dstpath, + srcpath + ]) + except FileNotFoundError: + raise RuntimeError('egg2bam failed: egg2bam was not found in the PATH') + except subprocess.SubprocessError as err: + raise RuntimeError('egg2bam failed: {}'.format(err)) return dstpath macosx_binary_magics = ( @@ -757,7 +762,10 @@ class build_apps(setuptools.Command): if ext in self.file_handlers: buildscript = self.file_handlers[ext] self.announce('running {} on src ({})'.format(buildscript.__name__, src)) - dst = self.file_handlers[ext](self, src, dst) + try: + dst = self.file_handlers[ext](self, src, dst) + except Exception as err: + self.announce('{}'.format(err), distutils.log.ERROR) else: self.announce('copying {0} -> {1}'.format(src, dst)) shutil.copyfile(src, dst) From 54655240b66943563b4db05c22c210ecb66f6212 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Fri, 7 Sep 2018 20:13:07 -0700 Subject: [PATCH 266/424] deploy-ng: Allow users to define custom package_data_dir items These are data (i.e., non-code) items copied for modules. --- direct/src/showutil/dist.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 1705b5fefd..92ec9d5d85 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -225,6 +225,7 @@ class build_apps(setuptools.Command): 'libgl.so.1', 'libx11.so.6', 'libreadline.so.5', 'libncursesw.so.5', 'libbz2.so.1', 'libz.so.1', 'liblzma.so.0', 'librt.so.1', 'libutil.so.1', ] + self.package_data_dirs = {} # We keep track of the zip files we've opened. self._zip_files = {} @@ -287,6 +288,10 @@ class build_apps(setuptools.Command): tmp.update(self.file_handlers) self.file_handlers = tmp + tmp = self.package_data_dirs.copy() + tmp.update(self.package_data_dirs) + self.package_data_dirs = tmp + def run(self): if not self.platforms: platforms = [p3d.PandaSystem.get_platform()] @@ -674,7 +679,7 @@ class build_apps(setuptools.Command): shutil.copytree(sub_dir, target_dir) # Extract any other data files from dependency packages. - for module, datadesc in PACKAGE_DATA_DIRS.items(): + for module, datadesc in self.package_data_dirs.items(): if module not in freezer_modules: continue From a2cb552375982c0a489f6b74e08901b90963134f Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 10 Sep 2018 20:04:08 -0700 Subject: [PATCH 267/424] deploy-ng: Only pass optimize=2 to compile() if the Python version is >=3.2 Fixes #388 --- direct/src/showutil/FreezeTool.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index a60b1efb89..83b1ea89f0 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1763,7 +1763,10 @@ class Freezer: code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext) else: code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) - code = compile(code, moduleName, 'exec', optimize=2) + if sys.version_info >= (3, 2): + code = compile(code, moduleName, 'exec', optimize=2) + else: + code = compile(code, moduleName, 'exec') code = marshal.dumps(code) moduleList.append((moduleName, len(pool), len(code))) pool += code From 8d8c5a546da1668740f01bd730799dd816ea7f44 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Sep 2018 14:50:00 +0200 Subject: [PATCH 268/424] input: rename "action_x" button handles to "face_x" --- panda/src/device/evdevInputDevice.cxx | 24 +++++++++--------- panda/src/device/ioKitInputDevice.cxx | 16 ++++++------ panda/src/device/linuxJoystickDevice.cxx | 2 +- panda/src/device/winRawInputDevice.cxx | 16 ++++++------ panda/src/device/xInputDevice.cxx | 9 +++---- panda/src/putil/gamepadButton.cxx | 32 ++++++++++++------------ panda/src/putil/gamepadButton.h | 16 ++++++------ 7 files changed, 57 insertions(+), 58 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index cf15b50f2a..72085f1ed9 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -906,10 +906,10 @@ map_button(int code, DeviceClass device_class) { if (device_class == DC_gamepad) { // Based on "Jess Tech Colour Rumble Pad" static const ButtonHandle mapping[] = { - GamepadButton::action_x(), - GamepadButton::action_y(), - GamepadButton::action_a(), - GamepadButton::action_b(), + GamepadButton::face_x(), + GamepadButton::face_y(), + GamepadButton::face_a(), + GamepadButton::face_b(), GamepadButton::lshoulder(), GamepadButton::ltrigger(), GamepadButton::rshoulder(), @@ -929,22 +929,22 @@ map_button(int code, DeviceClass device_class) { switch (code) { case BTN_A: - return GamepadButton::action_a(); + return GamepadButton::face_a(); case BTN_B: - return GamepadButton::action_b(); + return GamepadButton::face_b(); case BTN_C: - return GamepadButton::action_c(); + return GamepadButton::face_c(); case BTN_X: - return GamepadButton::action_x(); + return GamepadButton::face_x(); case BTN_Y: - return GamepadButton::action_y(); + return GamepadButton::face_y(); case BTN_Z: - return GamepadButton::action_z(); + return GamepadButton::face_z(); case BTN_TL: return GamepadButton::lshoulder(); @@ -959,10 +959,10 @@ map_button(int code, DeviceClass device_class) { return GamepadButton::rtrigger(); case BTN_1: - return GamepadButton::action_1(); + return GamepadButton::face_1(); case BTN_2: - return GamepadButton::action_2(); + return GamepadButton::face_2(); case BTN_SELECT: case KEY_PREVIOUS: diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index 3313e3f78a..0f0078404b 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -632,10 +632,10 @@ parse_element(IOHIDElementRef element) { // SNES-style USB gamepad static const ButtonHandle gamepad_buttons[] = { ButtonHandle::none(), - GamepadButton::action_x(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_y(), + GamepadButton::face_x(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_y(), GamepadButton::lshoulder(), GamepadButton::rshoulder(), ButtonHandle::none(), @@ -651,10 +651,10 @@ parse_element(IOHIDElementRef element) { // driver. I don't know if other drivers do the same thing at all. static const ButtonHandle gamepad_buttons[] = { ButtonHandle::none(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_x(), - GamepadButton::action_y(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_x(), + GamepadButton::face_y(), GamepadButton::lshoulder(), GamepadButton::rshoulder(), GamepadButton::lstick(), diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 2c10ede51c..e0f4d90348 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -134,7 +134,7 @@ open_device() { device_cat.debug() << "Unmapped /dev/input/js" << _index << " button " << (int)i << ": 0x" << std::hex << btnmap[i] << "\n"; } - } else if (handle == GamepadButton::action_a()) { + } else if (handle == GamepadButton::face_a()) { _device_class = DC_gamepad; } else if (handle == GamepadButton::trigger()) { _device_class = DC_flight_stick; diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index db8f82d76d..d61ad04d6e 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -332,10 +332,10 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // Do we have a button mapping? static const ButtonHandle gamepad_buttons_common[] = { ButtonHandle::none(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_x(), - GamepadButton::action_y(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_x(), + GamepadButton::face_y(), GamepadButton::lshoulder(), GamepadButton::rshoulder(), GamepadButton::start(), @@ -348,10 +348,10 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // SNES-style USB gamepad static const ButtonHandle gamepad_buttons_snes[] = { ButtonHandle::none(), - GamepadButton::action_x(), - GamepadButton::action_a(), - GamepadButton::action_b(), - GamepadButton::action_y(), + GamepadButton::face_x(), + GamepadButton::face_a(), + GamepadButton::face_b(), + GamepadButton::face_y(), GamepadButton::lshoulder(), GamepadButton::rshoulder(), ButtonHandle::none(), diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 7e1ddea132..680bbd26dd 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -386,11 +386,10 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { set_button_map(8, GamepadButton::lshoulder()); set_button_map(9, GamepadButton::rshoulder()); set_button_map(10, GamepadButton::guide()); - set_button_map(11, GamepadButton::action_a()); - set_button_map(12, GamepadButton::action_b()); - set_button_map(13, GamepadButton::action_x()); - set_button_map(14, GamepadButton::action_y()); - + set_button_map(11, GamepadButton::face_a()); + set_button_map(12, GamepadButton::face_b()); + set_button_map(13, GamepadButton::face_x()); + set_button_map(14, GamepadButton::face_y()); if (caps.Vibration.wLeftMotorSpeed != 0 || caps.Vibration.wRightMotorSpeed != 0) { diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index 12fae57b50..2924f0bf90 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -37,15 +37,15 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(start) DEFINE_GAMEPAD_BUTTON_HANDLE(next) DEFINE_GAMEPAD_BUTTON_HANDLE(previous) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_a) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_b) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_c) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_x) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_y) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_z) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_a) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_b) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_c) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_x) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_y) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_z) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_1) -DEFINE_GAMEPAD_BUTTON_HANDLE(action_2) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_1) +DEFINE_GAMEPAD_BUTTON_HANDLE(face_2) DEFINE_GAMEPAD_BUTTON_HANDLE(trigger) DEFINE_GAMEPAD_BUTTON_HANDLE(hat_up) @@ -100,15 +100,15 @@ init_gamepad_buttons() { ButtonRegistry::ptr()->register_button(_next, "next"); ButtonRegistry::ptr()->register_button(_previous, "previous"); - ButtonRegistry::ptr()->register_button(_action_a, "action_a"); - ButtonRegistry::ptr()->register_button(_action_b, "action_b"); - ButtonRegistry::ptr()->register_button(_action_c, "action_c"); - ButtonRegistry::ptr()->register_button(_action_x, "action_x"); - ButtonRegistry::ptr()->register_button(_action_y, "action_y"); - ButtonRegistry::ptr()->register_button(_action_z, "action_z"); + ButtonRegistry::ptr()->register_button(_face_a, "face_a"); + ButtonRegistry::ptr()->register_button(_face_b, "face_b"); + ButtonRegistry::ptr()->register_button(_face_c, "face_c"); + ButtonRegistry::ptr()->register_button(_face_x, "face_x"); + ButtonRegistry::ptr()->register_button(_face_y, "face_y"); + ButtonRegistry::ptr()->register_button(_face_z, "face_z"); - ButtonRegistry::ptr()->register_button(_action_1, "action_1"); - ButtonRegistry::ptr()->register_button(_action_2, "action_2"); + ButtonRegistry::ptr()->register_button(_face_1, "face_1"); + ButtonRegistry::ptr()->register_button(_face_2, "face_2"); ButtonRegistry::ptr()->register_button(_trigger, "trigger"); ButtonRegistry::ptr()->register_button(_hat_up, "hat_up"); diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index 768f634380..774ee8ef2f 100644 --- a/panda/src/putil/gamepadButton.h +++ b/panda/src/putil/gamepadButton.h @@ -43,15 +43,15 @@ PUBLISHED: static ButtonHandle next(); static ButtonHandle previous(); - static ButtonHandle action_a(); - static ButtonHandle action_b(); - static ButtonHandle action_c(); - static ButtonHandle action_x(); - static ButtonHandle action_y(); - static ButtonHandle action_z(); + static ButtonHandle face_a(); + static ButtonHandle face_b(); + static ButtonHandle face_c(); + static ButtonHandle face_x(); + static ButtonHandle face_y(); + static ButtonHandle face_z(); - static ButtonHandle action_1(); - static ButtonHandle action_2(); + static ButtonHandle face_1(); + static ButtonHandle face_2(); // Flight stick buttons, takes zero-based index. First is always trigger. static ButtonHandle trigger(); From 8edc01930774e3190144a9433ff45e5ba4160ae5 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Sep 2018 15:12:17 +0200 Subject: [PATCH 269/424] device: overhaul InputDevice API: - Rename "controls" terminology for analog inputs to "axes" - Change some naming of accessors - Unexpose methods where they are available as properties - Add serial_number field - Put battery levels under a "battery" field --- panda/src/device/analogNode.I | 6 +- panda/src/device/analogNode.cxx | 16 +- panda/src/device/buttonNode.I | 4 +- panda/src/device/clientAnalogDevice.cxx | 2 +- panda/src/device/evdevInputDevice.cxx | 30 ++-- panda/src/device/evdevInputDevice.h | 4 +- panda/src/device/inputDevice.I | 185 ++++++++++------------ panda/src/device/inputDevice.cxx | 131 ++++++++-------- panda/src/device/inputDevice.h | 187 ++++++++++++----------- panda/src/device/inputDeviceManager.h | 3 - panda/src/device/inputDeviceNode.cxx | 18 +++ panda/src/device/inputDeviceNode.h | 14 +- panda/src/device/ioKitInputDevice.cxx | 8 +- panda/src/device/linuxJoystickDevice.cxx | 48 +++--- panda/src/device/winRawInputDevice.cxx | 20 +-- panda/src/device/winRawInputDevice.h | 12 +- panda/src/device/xInputDevice.cxx | 143 ++++++++--------- panda/src/vrpn/vrpnAnalog.cxx | 8 +- panda/src/vrpn/vrpnTracker.cxx | 24 +-- 19 files changed, 439 insertions(+), 424 deletions(-) diff --git a/panda/src/device/analogNode.I b/panda/src/device/analogNode.I index 5fd3543c07..a61ff00b43 100644 --- a/panda/src/device/analogNode.I +++ b/panda/src/device/analogNode.I @@ -35,7 +35,7 @@ is_valid() const { */ INLINE int AnalogNode:: get_num_controls() const { - return _analog->get_num_controls(); + return _analog->get_num_axes(); } /** @@ -45,7 +45,7 @@ get_num_controls() const { */ INLINE double AnalogNode:: get_control_state(int index) const { - return _analog->get_control_state(index); + return _analog->get_axis_value(index); } /** @@ -54,7 +54,7 @@ get_control_state(int index) const { */ INLINE bool AnalogNode:: is_control_known(int index) const { - return _analog->is_control_known(index); + return _analog->is_axis_known(index); } /** diff --git a/panda/src/device/analogNode.cxx b/panda/src/device/analogNode.cxx index fffdadd579..6f9b42811c 100644 --- a/panda/src/device/analogNode.cxx +++ b/panda/src/device/analogNode.cxx @@ -81,7 +81,7 @@ write(std::ostream &out, int indent_level) const { DataNode::write(out, indent_level); if (_analog != nullptr) { - _analog->write_controls(out, indent_level + 2); + _analog->write_axes(out, indent_level + 2); } } @@ -101,12 +101,14 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, LPoint2 out(0.0f, 0.0f); for (int i = 0; i < max_outputs; i++) { - if (_outputs[i]._index >= 0 && - _analog->is_control_known(_outputs[i]._index)) { - if (_outputs[i]._flip) { - out[i] = -_analog->get_control_state(_outputs[i]._index); - } else { - out[i] = _analog->get_control_state(_outputs[i]._index); + if (_outputs[i]._index >= 0) { + InputDevice::AxisState state = _analog->get_axis(_outputs[i]._index); + if (state.known) { + if (_outputs[i]._flip) { + out[i] = -state.value; + } else { + out[i] = state.value; + } } } } diff --git a/panda/src/device/buttonNode.I b/panda/src/device/buttonNode.I index 1debd87ff7..013f66e41d 100644 --- a/panda/src/device/buttonNode.I +++ b/panda/src/device/buttonNode.I @@ -43,7 +43,7 @@ get_num_buttons() const { */ INLINE void ButtonNode:: set_button_map(int index, ButtonHandle button) { - _device->set_button_map(index, button); + _device->map_button(index, button); } /** @@ -62,7 +62,7 @@ get_button_map(int index) const { */ INLINE bool ButtonNode:: get_button_state(int index) const { - return _device->get_button_state(index); + return _device->is_button_pressed(index); } /** diff --git a/panda/src/device/clientAnalogDevice.cxx b/panda/src/device/clientAnalogDevice.cxx index 71fadfeb99..f9eef0f884 100644 --- a/panda/src/device/clientAnalogDevice.cxx +++ b/panda/src/device/clientAnalogDevice.cxx @@ -23,5 +23,5 @@ TypeHandle ClientAnalogDevice::_type_handle; void ClientAnalogDevice:: write(std::ostream &out, int indent_level) const { indent(out, indent_level) << get_type() << " " << get_name() << ":\n"; - write_controls(out, indent_level + 2); + write_axes(out, indent_level + 2); } diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 72085f1ed9..4fdd451cd5 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -392,10 +392,10 @@ init_device() { } if (test_bit(i, states)) { - button.state = S_down; + button._state = S_down; all_values_zero = false; } else { - button.state = S_up; + button._state = S_up; } if (button.handle == GamepadButton::dpad_left()) { emulate_dpad = false; @@ -415,7 +415,7 @@ init_device() { } if (has_axes) { - _control_indices.resize(num_bits, -1); + _axis_indices.resize(num_bits, -1); for (int i = 0; i < num_bits; ++i) { if (test_bit(i, axes)) { @@ -545,12 +545,12 @@ init_device() { std::swap(absinfo.maximum, absinfo.minimum); } if (axis == Axis::throttle && (quirks & QB_centered_throttle) != 0) { - index = add_control(axis, absinfo.maximum, absinfo.minimum, true); + index = add_axis(axis, absinfo.maximum, absinfo.minimum, true); } else { - index = add_control(axis, absinfo.minimum, absinfo.maximum); + index = add_axis(axis, absinfo.minimum, absinfo.maximum); } - control_changed(index, absinfo.value); - _control_indices[i] = index; + axis_changed(index, absinfo.value); + _axis_indices[i] = index; if (absinfo.value != 0) { all_values_zero = false; @@ -583,9 +583,9 @@ init_device() { if (_ltrigger_code >= 0 && _rtrigger_code >= 0 && !have_analog_triggers) { // Emulate analog triggers. - _ltrigger_control = (int)_controls.size(); - add_control(Axis::left_trigger, 0, 1, false); - add_control(Axis::right_trigger, 0, 1, false); + _ltrigger_axis = (int)_axes.size(); + add_axis(Axis::left_trigger, 0, 1, false); + add_axis(Axis::right_trigger, 0, 1, false); } else { _ltrigger_code = -1; _rtrigger_code = -1; @@ -709,10 +709,10 @@ process_events() { button_changed(_dpad_up_button, events[i].value < 0); button_changed(_dpad_up_button+1, events[i].value > 0); } - nassertd(code >= 0 && (size_t)code < _control_indices.size()) break; - index = _control_indices[code]; + nassertd(code >= 0 && (size_t)code < _axis_indices.size()) break; + index = _axis_indices[code]; if (index >= 0) { - control_changed(index, events[i].value); + axis_changed(index, events[i].value); } break; @@ -723,9 +723,9 @@ process_events() { button_changed(index, events[i].value != 0); } if (code == _ltrigger_code) { - control_changed(_ltrigger_control, events[i].value); + axis_changed(_ltrigger_axis, events[i].value); } else if (code == _rtrigger_code) { - control_changed(_ltrigger_control + 1, events[i].value); + axis_changed(_ltrigger_axis + 1, events[i].value); } break; diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index d82ba5f980..239ba78616 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -49,7 +49,7 @@ private: int _ff_strong; int _ff_weak; - pvector _control_indices; + pvector _axis_indices; pvector _button_indices; // These are used for D-pad emulation. @@ -59,7 +59,7 @@ private: int _dpad_up_button; // This is used for axis emulation. - int _ltrigger_control; + int _ltrigger_axis; int _ltrigger_code; int _rtrigger_code; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index a8eb5a127b..bcddc5cf52 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -24,9 +24,7 @@ InputDevice() : _product_id(0), _is_connected(false), _event_sequence(0), - _enable_pointer_events(false), - _battery_level(-1), - _max_battery_level(-1) + _enable_pointer_events(false) { _button_events = new ButtonEventList; } @@ -50,6 +48,16 @@ get_manufacturer() const { return _manufacturer; } +/** + * Returns a string containing the serial number of the device, if this + * information is known. + */ +INLINE std::string InputDevice:: +get_serial_number() const { + LightMutexHolder holder(_lock); + return _serial_number; +} + /** * Returns a string containing the USB vendor ID of the device, if this * information is known. @@ -160,23 +168,12 @@ get_tracker() const { /** * Returns a rough indication of the battery level, ranging from 0 (completely - * empty battery) to the number reported by get_max_battery_level(), which - * represents a full battery. If this information is not known, returns -1. + * empty battery) to the indicated max_level value. */ -INLINE short InputDevice:: -get_battery_level() const { +INLINE InputDevice::BatteryData InputDevice:: +get_battery() const { LightMutexHolder holder(_lock); - return _battery_level; -} - -/** - * Returns the maximum value that may be reported by get_battery_level(), - * representing a full battery. Returns -1 if no battery information is known. - */ -INLINE short InputDevice:: -get_max_battery_level() const { - LightMutexHolder holder(_lock); - return _max_battery_level; + return _battery_data; } /** @@ -202,7 +199,7 @@ get_num_buttons() const { * generate ButtonEvents when the buttons change state. */ INLINE void InputDevice:: -set_button_map(size_t index, ButtonHandle button) { +map_button(size_t index, ButtonHandle button) { LightMutexHolder holder(_lock); if (index >= _buttons.size()) { _buttons.resize(index + 1, ButtonState()); @@ -213,7 +210,7 @@ set_button_map(size_t index, ButtonHandle button) { /** * Returns the ButtonHandle that was previously associated with the given index - * number by a call to set_button_map(), or ButtonHandle::none() if no button + * number by a call to map_button(), or ButtonHandle::none() if no button * was associated. */ INLINE ButtonHandle InputDevice:: @@ -230,9 +227,9 @@ get_button_map(size_t index) const { * currently known to be down, or false if it is up or unknown. */ INLINE bool InputDevice:: -get_button_state(size_t index) const { +is_button_pressed(size_t index) const { if (index < _buttons.size()) { - return (_buttons[index].state == S_down); + return (_buttons[index]._state == S_down); } else { return false; } @@ -245,14 +242,14 @@ get_button_state(size_t index) const { INLINE bool InputDevice:: is_button_known(size_t index) const { if (index < _buttons.size()) { - return _buttons[index].state != S_unknown; + return _buttons[index]._state != S_unknown; } else { return false; } } /** - * Returns the ButtonState that is set at the given index, or throw an assection + * Returns the ButtonState that is set at the given index, or throw an assert * if the index was not found in the list. */ INLINE InputDevice::ButtonState InputDevice:: @@ -261,13 +258,13 @@ get_button(size_t index) const { return _buttons[index]; } else { device_cat.error() - << "Index " << index << " was not found in the controls list\n"; + << "Index " << index << " was not found in the axes list\n"; return ButtonState(); } } /** - * Returns the first ButtonState found with the given axis, or throw an assection + * Returns the first ButtonState found with the given axis, or throw an assert * if the button handle was not found in the list. */ INLINE InputDevice::ButtonState InputDevice:: @@ -278,103 +275,89 @@ find_button(ButtonHandle handle) const { } } device_cat.error() - << "Handle " << handle.get_name() << " was not found in the controls list\n"; + << "Handle " << handle.get_name() << " was not found in the axes list\n"; return ButtonState(); } /** - * Returns the number of analog controls known to the InputDevice. This number - * may change as more controls are discovered. + * Returns the number of analog axes known to the InputDevice. This number + * may change as more axes are discovered. */ INLINE size_t InputDevice:: -get_num_controls() const { - return _controls.size(); +get_num_axes() const { + return _axes.size(); } /** - * Associates the indicated Axis with the control of the indicated index + * Associates the indicated Axis with the axis of the indicated index * number. Pass Axis::none to turn off any association. * * It is not necessary to call this if you simply want to query the state of - * the various controls by index number. + * the various axes by index number. */ INLINE void InputDevice:: -set_control_map(size_t index, InputDevice::Axis axis) { +map_axis(size_t index, InputDevice::Axis axis) { LightMutexHolder holder(_lock); - if (index >= _controls.size()) { - _controls.resize(index + 1, AnalogState()); + if (index >= _axes.size()) { + _axes.resize(index + 1, AxisState()); } - _controls[index].axis = axis; + _axes[index].axis = axis; } /** - * Returns the Axis that was previously associated with the given index - * number by a call to set_control_map(), or Axis::none if no control was - * associated. - */ -INLINE InputDevice::Axis InputDevice:: -get_control_map(size_t index) const { - if (index < _controls.size()) { - return _controls[index].axis; - } else { - return Axis::none; - } -} - -/** - * Returns the current position of indicated analog control (identified by its - * index number), or 0.0 if the control is unknown. The normal range of a - * single control is -1.0 to 1.0. + * Returns the current position of indicated analog axis (identified by its + * index number), or 0.0 if the axis is unknown. The normal range of a + * single axis is -1.0 to 1.0. */ INLINE double InputDevice:: -get_control_state(size_t index) const { - if (index < _controls.size()) { - return _controls[index].state; +get_axis_value(size_t index) const { + if (index < _axes.size()) { + return _axes[index].value; } else { return 0.0; } } /** - * Returns the AnalogAxis that is set at the given index, or throw an assection + * Returns the axis state that is set at the given index, or throw an assert * if the index was not found in the list. */ -INLINE InputDevice::AnalogState InputDevice:: -get_control(size_t index) const { - if (index < _controls.size()) { - return _controls[index]; +INLINE InputDevice::AxisState InputDevice:: +get_axis(size_t index) const { + if (index < _axes.size()) { + return _axes[index]; } else { device_cat.error() - << "Index " << index<< " was not found in the controls list\n"; - return AnalogState(); + << "Index " << index << " was not found in the axes list\n"; + return AxisState(); } } /** - * Returns the first AnalogAxis found with the given axis, or throw an assection + * Returns the first AnalogAxis found with the given axis, or throw an assert * if the axis was not found in the list. */ -INLINE InputDevice::AnalogState InputDevice:: -find_control(InputDevice::Axis axis) const { - for (size_t i = 0; i < _controls.size(); ++i) { - if (_controls[i].axis == axis) { - return _controls[i]; +INLINE InputDevice::AxisState InputDevice:: +find_axis(InputDevice::Axis axis) const { + for (size_t i = 0; i < _axes.size(); ++i) { + if (_axes[i].axis == axis) { + return _axes[i]; } } device_cat.error() - << "Axis " << axis << " was not found in the controls list\n"; - return AnalogState(); + << "Axis " << axis << " was not found in the axes list\n"; + return AxisState(); } /** - * Returns true if the state of the indicated analog control is known, or false - * if we have never heard anything about this particular control. + * Returns true if the state of the indicated analog axis is known, or false + * if we have never heard anything about this particular axis. */ INLINE bool InputDevice:: -is_control_known(size_t index) const { - if (index < _controls.size()) { - return _controls[index].known; +is_axis_known(size_t index) const { + if (index < _axes.size()) { + return _axes[index].known; } else { return false; } @@ -382,8 +365,8 @@ is_control_known(size_t index) const { /** * Sets the strength of the vibration effect, if supported. The values are - * clamped to 0-1 range. The first value controls the low-frequency rumble - * motor, whereas the second controls the high-frequency motor, if present. + * clamped to 0-1 range. The first value axes the low-frequency rumble + * motor, whereas the second axes the high-frequency motor, if present. */ INLINE void InputDevice:: set_vibration(double strong, double weak) { @@ -420,36 +403,26 @@ set_connected(bool connected) { _is_connected = connected; } -/** - * - */ -INLINE bool InputDevice:: -operator == (const InputDevice &) const { - return true; -} - -/** - * - */ -INLINE bool InputDevice:: -operator != (const InputDevice &) const { - return false; -} - -/** - * - */ -INLINE bool InputDevice:: -operator < (const InputDevice &) const { - return false; -} - /** * */ INLINE InputDevice::ButtonState:: ButtonState(ButtonHandle handle) : - handle(handle), - state(S_unknown) -{ + handle(handle) { +} + +/** + * True if the button state is currently known. + */ +ALWAYS_INLINE bool InputDevice::ButtonState:: +is_known() const { + return (_state != S_unknown); +} + +/** + * True if the button is currently known to be pressed. + */ +ALWAYS_INLINE bool InputDevice::ButtonState:: +is_pressed() const { + return (_state == S_down); } diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index e24cd9a0df..aedfeb8222 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -32,8 +32,6 @@ InputDevice(const std::string &name, DeviceClass dev_class, int flags) : _is_connected(true), _event_sequence(0), _enable_pointer_events(false), - _battery_level(-1), - _max_battery_level(-1), _lock("InputDevice") { _button_events = new ButtonEventList; @@ -119,13 +117,22 @@ get_pointer_events() { return result; } - /** - * Called by the implementation to add a new known control. + * Called by the implementation to add a new known button. */ int InputDevice:: -add_control(Axis axis, int minimum, int maximum, bool centered) { - AnalogState state; +add_button(ButtonHandle button) { + int index = (int)_buttons.size(); + _buttons.push_back(ButtonState(button)); + return index; +} + +/** + * Called by the implementation to add a new known axis. + */ +int InputDevice:: +add_axis(Axis axis, int minimum, int maximum, bool centered) { + AxisState state; state.axis = axis; if (centered) { // Centered, eg. for sticks. @@ -136,17 +143,17 @@ add_control(Axis axis, int minimum, int maximum, bool centered) { state._scale = 1.0 / maximum; state._bias = 0.0; } - int index = (int)_controls.size(); - _controls.push_back(state); + int index = (int)_axes.size(); + _axes.push_back(state); return index; } /** - * Called by the implementation to add a new known control. This version - * tries to guess whether the control is centered or not. + * Called by the implementation to add a new known axis. This version tries + * to guess whether the axis is centered or not. */ int InputDevice:: -add_control(Axis axis, int minimum, int maximum) { +add_axis(Axis axis, int minimum, int maximum) { bool centered = (minimum < 0) || axis == Axis::x || axis == Axis::y @@ -160,7 +167,7 @@ add_control(Axis axis, int minimum, int maximum) { || axis == Axis::right_y || axis == Axis::wheel || axis == Axis::rudder; - return add_control(axis, minimum, maximum, centered); + return add_axis(axis, minimum, maximum, centered); } /** @@ -246,10 +253,10 @@ button_changed(int index, bool down) { } State new_state = down ? S_down : S_up; - if (_buttons[index].state == new_state) { + if (_buttons[index]._state == new_state) { return; } - _buttons[index].state = new_state; + _buttons[index]._state = new_state; ButtonHandle handle = _buttons[index].handle; @@ -272,65 +279,67 @@ button_changed(int index, bool down) { /** * Sets the state of the indicated analog index. The caller should ensure that * the lock is held while this call is made. This should be a number in the - * range -1.0 to 1.0, representing the current position of the control within - * its total range of movement. + * range -1.0 to 1.0, representing the current position of the axis within its + * total range of movement. */ void InputDevice:: -set_control_state(int index, double state) { - nassertv(_lock.debug_is_locked()); +set_axis_value(int index, double value) { + LightMutexHolder holder(_lock); + nassertv(index >= 0); - if (index >= (int)_controls.size()) { - _controls.resize(index + 1, AnalogState()); + if ((size_t)index >= _axes.size()) { + _axes.resize((size_t)index + 1u, AxisState()); } - if (device_cat.is_spam() && _controls[index].state != state) { + if (device_cat.is_spam() && _axes[index].value != value) { device_cat.spam() - << "Changed control " << index; + << "Changed axis " << index; - if (_controls[index].axis != Axis::none) { - device_cat.spam(false) << " (" << _controls[index].axis << ")"; + if (_axes[index].axis != Axis::none) { + device_cat.spam(false) << " (" << _axes[index].axis << ")"; } - device_cat.spam(false) << " to " << state << "\n"; + device_cat.spam(false) << " to " << value << "\n"; } - _controls[index].state = state; - _controls[index].known = true; + _axes[index].value = value; + _axes[index].known = true; } /** - * Like set_control_state, but instead passes a raw, unscaled value. + * Called by the implementation during do_poll to indicate that the indicated + * axis has received a new raw value. Assumes the lock is held. */ void InputDevice:: -control_changed(int index, int state) { +axis_changed(int index, int state) { nassertv(_lock.debug_is_locked()); nassertv(index >= 0); - if (index >= (int)_controls.size()) { - _controls.resize(index + 1, AnalogState()); + if ((size_t)index >= _axes.size()) { + _axes.resize((size_t)index + 1u, AxisState()); } - double new_state = fma((double)state, _controls[index]._scale, _controls[index]._bias); + double value = fma((double)state, _axes[index]._scale, _axes[index]._bias); - if (device_cat.is_spam() && _controls[index].state != new_state) { + if (device_cat.is_spam() && !IS_NEARLY_EQUAL(_axes[index].value, value)) { device_cat.spam() - << "Changed control " << index; + << "Changed axis " << index; - if (_controls[index].axis != Axis::none) { - device_cat.spam(false) << " (" << _controls[index].axis << ")"; + if (_axes[index].axis != Axis::none) { + device_cat.spam(false) << " (" << _axes[index].axis << ")"; } - device_cat.spam(false) << " to " << new_state << " (raw value " << state << ")\n"; + device_cat.spam(false) << " to " << value << " (raw value " << state << ")\n"; } - _controls[index].state = new_state; - _controls[index].known = true; + _axes[index].value = value; + _axes[index].known = true; } /** * Records that a tracker movement has taken place. */ void InputDevice:: -set_tracker(const LPoint3 &pos, const LOrientation &orient, double time) { +tracker_changed(const LPoint3 &pos, const LOrientation &orient, double time) { nassertv(_lock.debug_is_locked()); _tracker_data.set_pos(pos); @@ -364,11 +373,9 @@ output(std::ostream &out) const { } } - if (_controls.size() > 0) { - out << ", " << _controls.size() << " control"; - if (_controls.size() != 1) { - out.put('s'); - } + if (_axes.size() > 0) { + out << ", " << _axes.size() << " ax" + << (_axes.size() != 1 ? 'e' : 'i') << 's'; } if (_flags & IDF_has_pointer) { @@ -386,13 +393,13 @@ output(std::ostream &out) const { if (_flags & IDF_has_battery) { out << ", battery"; - if (_battery_level > 0 && _max_battery_level > 0) { + if (_battery_data.level > 0 && _battery_data.max_level > 0) { out << " ["; short i = 0; - for (; i < _battery_level; ++i) { + for (; i < _battery_data.level; ++i) { out << '='; } - for (; i < _max_battery_level; ++i) { + for (; i < _battery_data.max_level; ++i) { out << ' '; } out << ']'; @@ -411,13 +418,13 @@ output_buttons(std::ostream &out) const { Buttons::const_iterator bi; for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { const ButtonState &state = (*bi); - if (state.state != S_unknown) { + if (state.is_known()) { if (any_buttons) { out << ", "; } any_buttons = true; out << (int)(bi - _buttons.begin()) << "="; - if (state.state == S_up) { + if (state._state == S_up) { out << "up"; } else { out << "down"; @@ -439,7 +446,7 @@ write_buttons(std::ostream &out, int indent_level) const { Buttons::const_iterator bi; for (bi = _buttons.begin(); bi != _buttons.end(); ++bi) { const ButtonState &state = (*bi); - if (state.state != S_unknown) { + if (state.is_known()) { any_buttons = true; indent(out, indent_level) @@ -449,7 +456,7 @@ write_buttons(std::ostream &out, int indent_level) const { out << "(" << state.handle << ") "; } - if (state.state == S_up) { + if (state._state == S_up) { out << "up"; } else { out << "down"; @@ -465,27 +472,27 @@ write_buttons(std::ostream &out, int indent_level) const { } /** - * Writes a multi-line description of the current analog control states. + * Writes a multi-line description of the current analog axis states. */ void InputDevice:: -write_controls(std::ostream &out, int indent_level) const { +write_axes(std::ostream &out, int indent_level) const { LightMutexHolder holder(_lock); - bool any_controls = false; - Controls::const_iterator ai; - for (ai = _controls.begin(); ai != _controls.end(); ++ai) { - const AnalogState &state = (*ai); + bool any_axis = false; + Axes::const_iterator ai; + for (ai = _axes.begin(); ai != _axes.end(); ++ai) { + const AxisState &state = (*ai); if (state.known) { - any_controls = true; + any_axis = true; indent(out, indent_level) - << (int)(ai - _controls.begin()) << ". " << state.state << "\n"; + << (int)(ai - _axes.begin()) << ". " << state.value << "\n"; } } - if (!any_controls) { + if (!any_axis) { indent(out, indent_level) - << "(no known analog controls)\n"; + << "(no known analog axes)\n"; } } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 77b42b43c0..7e4d4f623b 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -33,12 +33,12 @@ typedef MouseData PointerData; /** * This is a structure representing a single input device. Input devices may - * have zero or more buttons, pointers, or controls associated with them, and + * have zero or more buttons, pointers, or axes associated with them, and * optionally a motion tracker. * * These devices are brought under a common interface because there is such a * large range of devices out there that may support any number of these types - * of controls, we couldn't even begin to cover them with type-specific + * of axes, we couldn't even begin to cover them with type-specific * subclasses. * * Use the various has_() and get_num_() methods to determine information about @@ -85,16 +85,6 @@ PUBLISHED: DC_COUNT, }; -protected: - InputDevice(const std::string &name, DeviceClass dev_class, int flags); - -public: - InputDevice(); - InputDevice(const InputDevice ©); - void operator = (const InputDevice ©); - ~InputDevice(); - -PUBLISHED: enum class Axis { none, @@ -126,19 +116,94 @@ PUBLISHED: brake, }; + enum State { + S_unknown, + S_up, + S_down + }; + + class ButtonState { + public: + constexpr ButtonState() = default; + INLINE ButtonState(ButtonHandle handle); + ALWAYS_INLINE bool is_known() const; + ALWAYS_INLINE bool is_pressed() const; + + PUBLISHED: + MAKE_PROPERTY(known, is_known); + MAKE_PROPERTY(pressed, is_pressed); + + ButtonHandle handle = ButtonHandle::none(); + + public: + State _state = S_unknown; + }; + + class AxisState { + public: + constexpr AxisState() = default; + + PUBLISHED: + Axis axis = Axis::none; + double value = 0.0; + bool known = false; + + public: + double _scale = 1.0; + double _bias = 0.0; + }; + + class BatteryData { + PUBLISHED: + // Ranges from 0 through max_level. + short level = -1; + + // Maximum value of 'level' field. + short max_level = -1; + }; + +protected: + InputDevice(const std::string &name, DeviceClass dev_class, int flags); + +public: + InputDevice(); + InputDevice(const InputDevice ©); + void operator = (const InputDevice ©); + ~InputDevice(); + INLINE std::string get_name() const; INLINE std::string get_manufacturer() const; + INLINE std::string get_serial_number() const; INLINE unsigned short get_vendor_id() const; INLINE unsigned short get_product_id() const; INLINE bool is_connected() const; INLINE DeviceClass get_device_class() const; + INLINE PointerData get_pointer() const; + INLINE TrackerData get_tracker() const; + INLINE BatteryData get_battery() const; + + INLINE size_t get_num_buttons() const; + INLINE ButtonHandle get_button_map(size_t index) const; + INLINE bool is_button_pressed(size_t index) const; + INLINE bool is_button_known(size_t index) const; + INLINE ButtonState get_button(size_t index) const; + + INLINE size_t get_num_axes() const; + INLINE double get_axis_value(size_t index) const; + INLINE bool is_axis_known(size_t index) const; + INLINE AxisState get_axis(size_t index) const; + +PUBLISHED: // The human-readable name of this input device. MAKE_PROPERTY(name, get_name); // The device's manufacturer, or the empty string if not known. MAKE_PROPERTY(manufacturer, get_manufacturer); + // The device's serial number, or the empty string if not known. + MAKE_PROPERTY(serial_number, get_serial_number); + // USB vendor ID of the device, or 0 if not known. MAKE_PROPERTY(vendor_id, get_vendor_id); @@ -159,24 +224,22 @@ PUBLISHED: INLINE bool has_vibration() const; INLINE bool has_battery() const; - INLINE PointerData get_pointer() const; - INLINE TrackerData get_tracker() const; + // Getters for the various types of device data. + MAKE_PROPERTY2(pointer, has_pointer, get_pointer); + MAKE_PROPERTY2(tracker, has_tracker, get_tracker); + MAKE_PROPERTY2(battery, has_battery, get_battery); - INLINE short get_battery_level() const; - INLINE short get_max_battery_level() const; + // Make device buttons and axes iterable + MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button); + MAKE_SEQ_PROPERTY(axes, get_num_axes, get_axis); - INLINE size_t get_num_buttons() const; - INLINE void set_button_map(size_t index, ButtonHandle button); - INLINE ButtonHandle get_button_map(size_t index) const; - INLINE bool get_button_state(size_t index) const; - INLINE bool is_button_known(size_t index) const; - - INLINE size_t get_num_controls() const; - INLINE void set_control_map(size_t index, Axis axis); - INLINE Axis get_control_map(size_t index) const; - INLINE double get_control_state(size_t index) const; - INLINE bool is_control_known(size_t index) const; + // Associate buttons/axes with symbolic handles. + INLINE void map_button(size_t index, ButtonHandle handle); + INLINE void map_axis(size_t index, Axis axis); + INLINE ButtonState find_button(ButtonHandle handle) const; + INLINE AxisState find_axis(Axis axis) const; + // Enable rumble force-feedback effects INLINE void set_vibration(double strong, double weak); INLINE void enable_pointer_events(); @@ -194,19 +257,20 @@ PUBLISHED: static std::string format_axis(Axis axis); protected: - // Called during the constructor to add new controls or buttons - int add_control(Axis axis, int minimum, int maximum, bool centered); - int add_control(Axis axis, int minimum, int maximum); + // Called during the constructor to add new axes or buttons + int add_button(ButtonHandle handle); + int add_axis(Axis axis, int minimum, int maximum, bool centered); + int add_axis(Axis axis, int minimum, int maximum); void set_pointer(bool inwin, double x, double y, double time); void set_pointer_out_of_window(double time); void pointer_moved(double x, double y, double time); void button_changed(int index, bool down); - void control_changed(int index, int value); - void set_control_state(int index, double state); + void axis_changed(int index, int value); + void set_axis_value(int index, double state); - void set_tracker(const LPoint3 &pos, const LOrientation &orient, double time); + void tracker_changed(const LPoint3 &pos, const LOrientation &orient, double time); virtual void do_set_vibration(double low, double high); virtual void do_poll(); @@ -214,16 +278,9 @@ protected: public: INLINE void set_connected(bool connected); - // We need these methods to make VC++ happy when we try to - // instantiate a pvector. They don't do - // anything useful. - INLINE bool operator == (const InputDevice &other) const; - INLINE bool operator != (const InputDevice &other) const; - INLINE bool operator < (const InputDevice &other) const; - void output_buttons(std::ostream &out) const; void write_buttons(std::ostream &out, int indent_level) const; - void write_controls(std::ostream &out, int indent_level) const; + void write_axes(std::ostream &out, int indent_level) const; protected: enum InputDeviceFlags { @@ -263,56 +320,14 @@ protected: PT(PointerEventList) _pointer_events; PUBLISHED: - enum State { - S_unknown, - S_up, - S_down - }; - - class ButtonState { - public: - constexpr ButtonState() = default; - INLINE ButtonState(ButtonHandle handle); - - PUBLISHED: - ButtonHandle handle = ButtonHandle::none(); - State state = S_unknown; - }; typedef pvector Buttons; + typedef pvector Axes; Buttons _buttons; + Axes _axes; - class AnalogState { - public: - constexpr AnalogState() = default; - - PUBLISHED: - Axis axis = Axis::none; - double state = 0.0; - bool known = false; - - public: - double _scale = 1.0; - double _bias = 0.0; - }; - typedef pvector Controls; - Controls _controls; - - short _battery_level; - short _max_battery_level; - + BatteryData _battery_data; TrackerData _tracker_data; - - INLINE ButtonState get_button(size_t index) const; - INLINE ButtonState find_button(ButtonHandle handle) const; - - INLINE AnalogState get_control(size_t index) const; - INLINE AnalogState find_control(Axis axis) const; - - // Make device buttons and controls iterable - MAKE_SEQ_PROPERTY(buttons, get_num_buttons, get_button); - MAKE_SEQ_PROPERTY(controls, get_num_controls, get_control); - public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index e231032a34..9cfadf2845 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -46,9 +46,6 @@ PUBLISHED: INLINE static InputDeviceManager *get_global_ptr(); - // The set of all currently connected devices. - MAKE_PROPERTY(devices, get_devices); - protected: LightMutex _lock; diff --git a/panda/src/device/inputDeviceNode.cxx b/panda/src/device/inputDeviceNode.cxx index 0f22cbe7f2..712d377562 100644 --- a/panda/src/device/inputDeviceNode.cxx +++ b/panda/src/device/inputDeviceNode.cxx @@ -54,9 +54,27 @@ get_device() const { void InputDeviceNode:: do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { + + if (_device == nullptr && !_device->is_connected()) { + return; + } + + _device->poll(); + // get all button events of the device and forward them to the data graph if (_device->has_button_event()) { PT(ButtonEventList) bel = _device->get_button_events(); + + // Register the current state for each button. + for (int i = 0; i < bel->get_num_events(); ++i) { + const ButtonEvent &event = bel->get_event(i); + if (event._type == ButtonEvent::T_down) { + _button_states[event._button] = true; + } else if (event._type == ButtonEvent::T_down) { + _button_states[event._button] = false; + } + } + output.set_data(_button_events_output, EventParameter(bel)); } diff --git a/panda/src/device/inputDeviceNode.h b/panda/src/device/inputDeviceNode.h index 659ed5bf71..8e0d20e96a 100644 --- a/panda/src/device/inputDeviceNode.h +++ b/panda/src/device/inputDeviceNode.h @@ -21,17 +21,22 @@ #include "linmath_events.h" /** - * Reads the controler data sent from the InputDeviceManager, and - * transmits it down the data graph. - * + * Reads the controller data sent from the InputDeviceManager, and transmits + * it down the data graph. * + * This is intended to only be accessed from the app thread. */ class EXPCL_PANDA_DEVICE InputDeviceNode : public DataNode { PUBLISHED: InputDeviceNode(InputDevice *device, const std::string &name); + +public: void set_device(InputDevice *device); PT(InputDevice) get_device() const; +PUBLISHED: + MAKE_PROPERTY(device, get_device, set_device); + protected: // Inherited from DataNode virtual void do_transmit_data(DataGraphTraverser *trav, @@ -39,6 +44,9 @@ protected: DataNodeTransmit &output); private: + pmap _button_states; + pmap _control_states; + // outputs int _button_events_output; int _low_battery_event_output; diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index 0f0078404b..b3505e084f 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -287,14 +287,14 @@ parse_element(IOHIDElementRef element) { int max = IOHIDElementGetLogicalMax(element); if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. - add_control(axis, max, min, true); + add_axis(axis, max, min, true); } else if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { // We'd like to reverse the Y axis to match the XInput behavior. // We also reverse yaw to obey the right-hand rule. - add_control(axis, max, min); + add_axis(axis, max, min); } else { - add_control(axis, min, max); + add_axis(axis, min, max); } _analog_elements.push_back(element); @@ -734,7 +734,7 @@ do_poll() { IOHIDValueRef value_ref; if (IOHIDDeviceGetValue(_device, _analog_elements[i], &value_ref) == kIOReturnSuccess) { int value = IOHIDValueGetIntegerValue(value_ref); - control_changed(i, value); + axis_changed(i, value); } } diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index e0f4d90348..ccdd29d553 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -121,7 +121,7 @@ open_device() { ioctl(_fd, JSIOCGBUTTONS, &num_buttons); _buttons.resize(num_buttons); - _controls.resize(num_axes); + _axes.resize(num_axes); if (num_buttons > 0) { uint16_t btnmap[512]; @@ -225,11 +225,11 @@ open_device() { _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); if (_device_class == DC_gamepad) { - _buttons.push_back(ButtonState(GamepadButton::dpad_left())); - _buttons.push_back(ButtonState(GamepadButton::dpad_right())); + add_button(GamepadButton::dpad_left()); + add_button(GamepadButton::dpad_right()); } else { - _buttons.push_back(ButtonState(GamepadButton::hat_left())); - _buttons.push_back(ButtonState(GamepadButton::hat_right())); + add_button(GamepadButton::hat_left()); + add_button(GamepadButton::hat_right()); } axis = Axis::none; } @@ -241,11 +241,11 @@ open_device() { _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); if (_device_class == DC_gamepad) { - _buttons.push_back(ButtonState(GamepadButton::dpad_up())); - _buttons.push_back(ButtonState(GamepadButton::dpad_down())); + add_button(GamepadButton::dpad_up()); + add_button(GamepadButton::dpad_down()); } else { - _buttons.push_back(ButtonState(GamepadButton::hat_up())); - _buttons.push_back(ButtonState(GamepadButton::hat_down())); + add_button(GamepadButton::hat_up()); + add_button(GamepadButton::hat_down()); } axis = Axis::none; } @@ -259,28 +259,28 @@ open_device() { axis = Axis::none; break; } - _controls[i].axis = axis; + _axes[i].axis = axis; if (axis == Axis::left_trigger || axis == Axis::right_trigger) { // We'd like to use 0.0 to indicate the resting position. - _controls[i]._scale = 1.0 / 65534.0; - _controls[i]._bias = 0.5; + _axes[i]._scale = 1.0 / 65534.0; + _axes[i]._bias = 0.5; have_analog_triggers = true; } else if (axis == Axis::left_y || axis == Axis::right_y || axis == Axis::y) { - _controls[i]._scale = 1.0 / -32767.0; - _controls[i]._bias = 0.0; + _axes[i]._scale = 1.0 / -32767.0; + _axes[i]._bias = 0.0; } else { - _controls[i]._scale = 1.0 / 32767.0; - _controls[i]._bias = 0.0; + _axes[i]._scale = 1.0 / 32767.0; + _axes[i]._bias = 0.0; } } } if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) { // Emulate analog triggers. - _ltrigger_control = (int)_controls.size(); - add_control(Axis::left_trigger, 0, 1, false); - add_control(Axis::right_trigger, 0, 1, false); + _ltrigger_control = (int)_axes.size(); + add_axis(Axis::left_trigger, 0, 1, false); + add_axis(Axis::right_trigger, 0, 1, false); } else { _ltrigger_button = -1; _rtrigger_button = -1; @@ -343,8 +343,8 @@ open_device() { // are all 0, which indicates that the driver hasn't received any data for // this gamepad yet (which means it hasn't been plugged in for this session) if (strncmp(name, "Xbox 360 Wireless Receiver", 26) == 0) { - for (const auto &control : _controls) { - if (control.state != 0.0) { + for (const auto &control : _axes) { + if (control.value != 0.0) { _is_connected = true; return true; } @@ -398,9 +398,9 @@ process_events() { if (events[i].type & JS_EVENT_BUTTON) { if (index == _ltrigger_button) { - control_changed(_ltrigger_control, events[i].value); + axis_changed(_ltrigger_control, events[i].value); } else if (index == _rtrigger_button) { - control_changed(_ltrigger_control + 1, events[i].value); + axis_changed(_ltrigger_control + 1, events[i].value); } button_changed(index, (events[i].value != 0)); @@ -413,7 +413,7 @@ process_events() { button_changed(_dpad_up_button+1, events[i].value > 1000); } - control_changed(index, events[i].value); + axis_changed(index, events[i].value); } } diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index d61ad04d6e..7aa520a1fe 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -362,11 +362,11 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { gamepad_buttons = gamepad_buttons_snes; } - // Prepare a mapping of data indices to button/control indices. + // Prepare a mapping of data indices to button/axis indices. _indices.resize(caps.NumberInputDataIndices); _buttons.clear(); - _controls.clear(); + _axes.clear(); USHORT num_button_caps = caps.NumberInputButtonCaps; PHIDP_BUTTON_CAPS button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS)); @@ -553,17 +553,17 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; } - int control_index; + int axis_index; if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { // T.Flight Hotas X throttle is reversed and can go backwards. - control_index = add_control(axis, cap.LogicalMax, cap.LogicalMin, true); + axis_index = add_axis(axis, cap.LogicalMax, cap.LogicalMin, true); } else if (!is_signed) { // All axes on the weird XInput-style mappings go from -1 to 1 - control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax, true); + axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax, true); } else { - control_index = add_control(axis, cap.LogicalMin, cap.LogicalMax); + axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax); } - _indices[data_index] = Index::control(control_index, is_signed); + _indices[data_index] = Index::axis(axis_index, is_signed); } } @@ -635,11 +635,11 @@ on_input(PRAWINPUT input) { for (ULONG di = 0; di < count; ++di) { if (data[di].DataIndex != _hat_data_index) { const Index &idx = _indices[data[di].DataIndex]; - if (idx._control >= 0) { + if (idx._axis >= 0) { if (idx._signed) { - control_changed(idx._control, (SHORT)data[di].RawValue); + axis_changed(idx._axis, (SHORT)data[di].RawValue); } else { - control_changed(idx._control, data[di].RawValue); + axis_changed(idx._axis, data[di].RawValue); } } if (idx._button >= 0) { diff --git a/panda/src/device/winRawInputDevice.h b/panda/src/device/winRawInputDevice.h index 95ddc59cc5..c5cba8c615 100644 --- a/panda/src/device/winRawInputDevice.h +++ b/panda/src/device/winRawInputDevice.h @@ -49,28 +49,28 @@ private: // Indexed by report ID pvector _report_buttons; - // Either a button index or a control index. + // Either a button index or a axis index. struct Index { - Index() : _button(-1), _control(-1) {} + Index() : _button(-1), _axis(-1) {} static Index button(int index) { Index idx; idx._button = index; return idx; } - static Index control(int index, bool is_signed=true) { + static Index axis(int index, bool is_signed=true) { Index idx; - idx._control = index; + idx._axis = index; idx._signed = is_signed; return idx; } int _button; - int _control; + int _axis; bool _signed; }; - // Maps a "data index" to either button index or control index. + // Maps a "data index" to either button index or axis index. pvector _indices; int _hat_data_index; int _hat_data_minimum; diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 680bbd26dd..40cb56959c 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -126,7 +126,7 @@ XInputDevice(DWORD user_index) : nassertv(user_index >= 0 && user_index < XUSER_MAX_COUNT); - _controls.resize(6); + _axes.resize(6); _buttons.resize(16); } @@ -301,6 +301,7 @@ init_xinput() { /** * Initializes the device. Called when the device was just connected. + * Assumes either the lock is held or this is called from the constructor. */ void XInputDevice:: init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { @@ -315,81 +316,81 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { default: case XINPUT_DEVSUBTYPE_GAMEPAD: _device_class = DC_gamepad; - set_control_map(0, Axis::left_trigger); - set_control_map(1, Axis::right_trigger); - set_control_map(2, Axis::left_x); - set_control_map(3, Axis::left_y); - set_control_map(4, Axis::right_x); - set_control_map(5, Axis::right_y); + _axes[0].axis = Axis::left_trigger; + _axes[1].axis = Axis::right_trigger; + _axes[2].axis = Axis::left_x; + _axes[3].axis = Axis::left_y; + _axes[4].axis = Axis::right_x; + _axes[5].axis = Axis::right_y; break; case XINPUT_DEVSUBTYPE_WHEEL: _device_class = DC_steering_wheel; - set_control_map(0, Axis::brake); - set_control_map(1, Axis::accelerator); - set_control_map(2, Axis::wheel); - set_control_map(3, Axis::none); - set_control_map(4, Axis::none); - set_control_map(5, Axis::none); + _axes[0].axis = Axis::brake; + _axes[1].axis = Axis::accelerator; + _axes[2].axis = Axis::wheel; + _axes[3].axis = Axis::none; + _axes[4].axis = Axis::none; + _axes[5].axis = Axis::none; break; case XINPUT_DEVSUBTYPE_FLIGHT_STICK: _device_class = DC_flight_stick; - set_control_map(0, Axis::yaw); - set_control_map(1, Axis::throttle); - set_control_map(2, Axis::roll); - set_control_map(3, Axis::pitch); - set_control_map(4, Axis::none); - set_control_map(5, Axis::none); + _axes[0].axis = Axis::yaw; + _axes[1].axis = Axis::throttle; + _axes[2].axis = Axis::roll; + _axes[3].axis = Axis::pitch; + _axes[4].axis = Axis::none; + _axes[5].axis = Axis::none; break; case XINPUT_DEVSUBTYPE_DANCE_PAD: _device_class = DC_dance_pad; - set_control_map(0, Axis::none); - set_control_map(1, Axis::none); - set_control_map(2, Axis::none); - set_control_map(3, Axis::none); - set_control_map(4, Axis::none); - set_control_map(5, Axis::none); + _axes[0].axis = Axis::none; + _axes[1].axis = Axis::none; + _axes[2].axis = Axis::none; + _axes[3].axis = Axis::none; + _axes[4].axis = Axis::none; + _axes[5].axis = Axis::none; break; } - _controls[0]._scale = 1.0 / 255.0; - _controls[1]._scale = 1.0 / 255.0; - _controls[2]._scale = 1.0 / 32767.5; - _controls[3]._scale = 1.0 / 32767.5; - _controls[4]._scale = 1.0 / 32767.5; - _controls[5]._scale = 1.0 / 32767.5; + _axes[0]._scale = 1.0 / 255.0; + _axes[1]._scale = 1.0 / 255.0; + _axes[2]._scale = 1.0 / 32767.5; + _axes[3]._scale = 1.0 / 32767.5; + _axes[4]._scale = 1.0 / 32767.5; + _axes[5]._scale = 1.0 / 32767.5; - _controls[2]._bias = 0.5 / 32767.5; - _controls[3]._bias = 0.5 / 32767.5; - _controls[4]._bias = 0.5 / 32767.5; - _controls[5]._bias = 0.5 / 32767.5; + _axes[2]._bias = 0.5 / 32767.5; + _axes[3]._bias = 0.5 / 32767.5; + _axes[4]._bias = 0.5 / 32767.5; + _axes[5]._bias = 0.5 / 32767.5; if (caps.Flags & XINPUT_CAPS_NO_NAVIGATION) { - set_button_map(0, ButtonHandle::none()); - set_button_map(1, ButtonHandle::none()); - set_button_map(2, ButtonHandle::none()); - set_button_map(3, ButtonHandle::none()); - set_button_map(4, ButtonHandle::none()); - set_button_map(5, ButtonHandle::none()); + _buttons[0].handle = ButtonHandle::none(); + _buttons[1].handle = ButtonHandle::none(); + _buttons[2].handle = ButtonHandle::none(); + _buttons[3].handle = ButtonHandle::none(); + _buttons[4].handle = ButtonHandle::none(); + _buttons[5].handle = ButtonHandle::none(); } else { - set_button_map(0, GamepadButton::dpad_up()); - set_button_map(1, GamepadButton::dpad_down()); - set_button_map(2, GamepadButton::dpad_left()); - set_button_map(3, GamepadButton::dpad_right()); - set_button_map(4, GamepadButton::start()); - set_button_map(5, GamepadButton::back()); + _buttons[0].handle = GamepadButton::dpad_up(); + _buttons[1].handle = GamepadButton::dpad_down(); + _buttons[2].handle = GamepadButton::dpad_left(); + _buttons[3].handle = GamepadButton::dpad_right(); + _buttons[4].handle = GamepadButton::start(); + _buttons[5].handle = GamepadButton::back(); } - set_button_map(6, GamepadButton::lstick()); - set_button_map(7, GamepadButton::rstick()); - set_button_map(8, GamepadButton::lshoulder()); - set_button_map(9, GamepadButton::rshoulder()); - set_button_map(10, GamepadButton::guide()); - set_button_map(11, GamepadButton::face_a()); - set_button_map(12, GamepadButton::face_b()); - set_button_map(13, GamepadButton::face_x()); - set_button_map(14, GamepadButton::face_y()); + _buttons[6].handle = GamepadButton::lstick(); + _buttons[7].handle = GamepadButton::rstick(); + _buttons[8].handle = GamepadButton::lshoulder(); + _buttons[9].handle = GamepadButton::rshoulder(); + _buttons[10].handle = GamepadButton::guide(); + _buttons[11].handle = GamepadButton::face_a(); + _buttons[12].handle = GamepadButton::face_b(); + _buttons[13].handle = GamepadButton::face_x(); + _buttons[14].handle = GamepadButton::face_y(); if (caps.Vibration.wLeftMotorSpeed != 0 || caps.Vibration.wRightMotorSpeed != 0) { @@ -403,8 +404,8 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { batt.BatteryType != BATTERY_TYPE_WIRED) { // This device has a battery. Report the battery level. _flags |= IDF_has_battery; - _battery_level = batt.BatteryLevel; - _max_battery_level = BATTERY_LEVEL_FULL; + _battery_data.level = batt.BatteryLevel; + _battery_data.max_level = BATTERY_LEVEL_FULL; } } } @@ -413,7 +414,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { WORD mask = 1; for (int i = 0; i < 16; ++i) { // Set the state without triggering a button event. - _buttons[i].state = (buttons & mask) ? S_down : S_up; + _buttons[i]._state = (buttons & mask) ? S_down : S_up; mask <<= 1; if (i == 10) { // XInput skips 0x0800. @@ -421,12 +422,12 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { } } - control_changed(0, state.Gamepad.bLeftTrigger); - control_changed(1, state.Gamepad.bRightTrigger); - control_changed(2, state.Gamepad.sThumbLX); - control_changed(3, state.Gamepad.sThumbLY); - control_changed(4, state.Gamepad.sThumbRX); - control_changed(5, state.Gamepad.sThumbRY); + axis_changed(0, state.Gamepad.bLeftTrigger); + axis_changed(1, state.Gamepad.bRightTrigger); + axis_changed(2, state.Gamepad.sThumbLX); + axis_changed(3, state.Gamepad.sThumbLY); + axis_changed(4, state.Gamepad.sThumbRX); + axis_changed(5, state.Gamepad.sThumbRY); _last_buttons = buttons; _last_packet = state.dwPacketNumber; @@ -491,12 +492,12 @@ do_poll() { } } - control_changed(0, state.Gamepad.bLeftTrigger); - control_changed(1, state.Gamepad.bRightTrigger); - control_changed(2, state.Gamepad.sThumbLX); - control_changed(3, state.Gamepad.sThumbLY); - control_changed(4, state.Gamepad.sThumbRX); - control_changed(5, state.Gamepad.sThumbRY); + axis_changed(0, state.Gamepad.bLeftTrigger); + axis_changed(1, state.Gamepad.bRightTrigger); + axis_changed(2, state.Gamepad.sThumbLX); + axis_changed(3, state.Gamepad.sThumbLY); + axis_changed(4, state.Gamepad.sThumbRX); + axis_changed(5, state.Gamepad.sThumbRY); _last_buttons = state.Gamepad.wButtons; _last_packet = state.dwPacketNumber; diff --git a/panda/src/vrpn/vrpnAnalog.cxx b/panda/src/vrpn/vrpnAnalog.cxx index c24beabb48..2745aab75e 100644 --- a/panda/src/vrpn/vrpnAnalog.cxx +++ b/panda/src/vrpn/vrpnAnalog.cxx @@ -100,13 +100,7 @@ vrpn_analog_callback(void *userdata, const vrpn_ANALOGCB info) { for (di = self->_devices.begin(); di != self->_devices.end(); ++di) { VrpnAnalogDevice *device = (*di); for (int i = 0; i < info.num_channel; i++) { - if (vrpn_cat.is_debug()) { - if (device->get_control_state(i) != info.channel[i]) { - vrpn_cat.debug() - << *self << " got analog " << i << " = " << info.channel[i] << "\n"; - } - } - device->set_control_state(i, info.channel[i]); + device->set_axis_value(i, info.channel[i]); } } } diff --git a/panda/src/vrpn/vrpnTracker.cxx b/panda/src/vrpn/vrpnTracker.cxx index b42f4f5336..e1b08e2a72 100644 --- a/panda/src/vrpn/vrpnTracker.cxx +++ b/panda/src/vrpn/vrpnTracker.cxx @@ -107,10 +107,10 @@ vrpn_position_callback(void *userdata, const vrpn_TRACKERCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_position) { - device->set_tracker(LPoint3(info.pos[0], info.pos[1], info.pos[2]), - LOrientation(info.quat[3], info.quat[0], - info.quat[1], info.quat[2]), - VrpnClient::convert_to_secs(info.msg_time)); + device->tracker_changed(LPoint3(info.pos[0], info.pos[1], info.pos[2]), + LOrientation(info.quat[3], info.quat[0], + info.quat[1], info.quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } @@ -132,10 +132,10 @@ vrpn_velocity_callback(void *userdata, const vrpn_TRACKERVELCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_velocity) { - device->set_tracker(LPoint3(info.vel[0], info.vel[1], info.vel[2]), - LOrientation(info.vel_quat[3], info.vel_quat[0], - info.vel_quat[1], info.vel_quat[2]), - VrpnClient::convert_to_secs(info.msg_time)); + device->tracker_changed(LPoint3(info.vel[0], info.vel[1], info.vel[2]), + LOrientation(info.vel_quat[3], info.vel_quat[0], + info.vel_quat[1], info.vel_quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } @@ -157,10 +157,10 @@ vrpn_acceleration_callback(void *userdata, const vrpn_TRACKERACCCB info) { VrpnTrackerDevice *device = (*di); if (device->get_sensor() == info.sensor && device->get_data_type() == VrpnTrackerDevice::DT_acceleration) { - device->set_tracker(LPoint3(info.acc[0], info.acc[1], info.acc[2]), - LOrientation(info.acc_quat[3], info.acc_quat[0], - info.acc_quat[1], info.acc_quat[2]), - VrpnClient::convert_to_secs(info.msg_time)); + device->tracker_changed(LPoint3(info.acc[0], info.acc[1], info.acc[2]), + LOrientation(info.acc_quat[3], info.acc_quat[0], + info.acc_quat[1], info.acc_quat[2]), + VrpnClient::convert_to_secs(info.msg_time)); } } } From 76365f3ed19318400e172e7e5c4ea7c2e62e0ac8 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Sep 2018 20:22:40 +0200 Subject: [PATCH 270/424] showbase: make input device binding explicit, not automatic --- direct/src/showbase/ShowBase.py | 109 ++++++++++++-------------------- 1 file changed, 41 insertions(+), 68 deletions(-) diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 5a7a647c0a..8ed91bd8af 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -307,12 +307,7 @@ class ShowBase(DirectObject.DirectObject): ## This is the global input device manager, which keeps track of ## connected input devices. self.devices = InputDeviceManager.getGlobalPtr() - # add existing devices to the data graph - for device in self.devices.devices: - self.connectDevice(device) - # Checks for device connection and disconnection - self.accept('connect-device', self.connectDevice) - self.accept('disconnect-device', self.disconnectDevice) + self.__inputDeviceNodes = {} self.createStats() @@ -1681,76 +1676,56 @@ class ShowBase(DirectObject.DirectObject): return self.mouseWatcherNode.getModifierButtons().isDown( KeyboardButton.meta()) - def connectDevice(self, device): + def attachInputDevice(self, device, prefix=None): """ - This function will get called each time a new device got - connected and will add that new device to the data graph. + This function attaches an input device to the data graph, which will + cause the device to be polled and generate events. If a prefix is + given and not None, it is used to prefix events generated by this + device, separated by a hyphen. - Each device class will get a specific prefix for thrown events. Those - are currently as follow - - gamepad - flight_stick - steering_wheel - dance_pad - mouse - keyboard - unclassified_device - - In addition, the index of that device will appended to the prefix, - so for example if you hit the A button of the first connected gamepad - you will get an event like "gamepad0-action_a" the second gamepad will - then be catchable via "gamepad1-button_event" and so on. - Note, each device class will have a separate 0 based index, this way - you can have a gamepad0 as well as a steering_wheel0 and flight_stick0. - - All newly created button throwers will be stored in - the deviceButtonThrowers lsit + If you call this, you should consider calling detachInputDevice when + you are done with the device or when it is disconnected. """ - idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.getName())) - prefix = "unclassified_device" - if device.getDeviceClass() == InputDevice.DC_gamepad: - prefix = "gamepad" - elif device.getDeviceClass() == InputDevice.DC_flight_stick: - prefix = "flight_stick" - elif device.getDeviceClass() == InputDevice.DC_steering_wheel: - prefix = "steering_wheel" - elif device.getDeviceClass() == InputDevice.DC_dance_pad: - prefix = "dance_pad" - elif device.getDeviceClass() == InputDevice.DC_mouse: - prefix = "mouse" - elif device.getDeviceClass() == InputDevice.DC_keyboard: - prefix = "keyboard" - currentPrefixes = [] - for np in self.dataRoot.findAllMatches("**/{}".format(prefix)): - bt = np.node() - currentPrefixes.append(bt.getPrefix()) + # Protect against the same device being attached multiple times. + assert device not in self.__inputDeviceNodes - id = 0 - # Find the next free ID for the newly connected device - while "{}{}-".format(prefix, id) in currentPrefixes: - id+=1 - # Setup the button thrower for that device and register it's event prefix - bt = idn.attachNewNode(ButtonThrower(prefix)) - assert self.notify.debug("Registered event prefix {}{}-".format(prefix, id)) - bt.node().setPrefix("{}{}-".format(prefix, id)) - # append the new button thrower to the list of device button throwers + idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name)) + + # Setup the button thrower to generate events for the device. + bt = idn.attachNewNode(ButtonThrower(device.name)) + if prefix is not None: + bt.node().setPrefix(prefix + '-') + + assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix)) + self.__inputDeviceNodes[device] = idn self.deviceButtonThrowers.append(bt) - def disconnectDevice(self, device): + def detachInputDevice(self, device): """ - This function will get called each time a new device got - connected. It is then used to clean up the given device from the - data graph. + This should be called after attaching an input device using + attachInputDevice and the device is disconnected or you no longer wish + to keep polling this device for events. + + You do not strictly need to call this if you expect the device to be + reconnected (but be careful that you don't reattach it). """ - self.notify.debug("Disconnect device {}".format(device.getName())) - idn = self.dataRoot.find("**/{}".format(device.getName())) - for bt in list(self.deviceButtonThrowers): - if bt.getName() == idn.getName(): + + if device not in self.__inputDeviceNodes: + assert device in self.__inputDeviceNodes + return + + assert self.notify.debug("Detached device {0}".format(device.name)) + + # Remove the ButtonThrower from the deviceButtonThrowers list. + idn = self.__inputDeviceNodes[device] + for bt in self.deviceButtonThrowers: + if idn.isAncestorOf(bt): self.deviceButtonThrowers.remove(bt) break + idn.removeNode() + del self.__inputDeviceNodes[device] def addAngularIntegrator(self): if not self.physicsMgrAngular: @@ -1944,10 +1919,6 @@ class ShowBase(DirectObject.DirectObject): # Check if there were newly connected devices. self.devices.update() - # Poll all connected devices. - for device in self.devices.devices: - device.poll() - # traverse the data graph. This reads all the control # inputs (from the mouse and keyboard, for instance) and also # directly acts upon them (for instance, to move the avatar). @@ -3163,6 +3134,8 @@ class ShowBase(DirectObject.DirectObject): setup_mouse = setupMouse setup_mouse_cb = setupMouseCB enable_software_mouse_pointer = enableSoftwareMousePointer + detach_input_device = detachInputDevice + attach_input_device = attachInputDevice add_angular_integrator = addAngularIntegrator enable_particles = enableParticles disable_particles = disableParticles From 3a206ed7c463e00926c73c2f18a97c6d5d89b86c Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 17 Sep 2018 17:58:01 +0200 Subject: [PATCH 271/424] makewheel: ignore dependencies of panda3d_tools/pstats --- makepanda/makewheel.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 4931dc1094..2f4680ae69 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -107,6 +107,11 @@ MANYLINUX_LIBS = [ "linux-vdso.so.1", "linux-gate.so.1", "ld-linux.so.2", ] +# Binaries to never scan for dependencies on non-Windows systems. +IGNORE_UNIX_DEPS_OF = [ + "panda3d_tools/pstats", +] + WHEEL_DATA = """Wheel-Version: 1.0 Generator: makepanda Root-Is-Purelib: false @@ -328,11 +333,16 @@ class WheelFile(object): # If this is a .so file, we should set the rpath appropriately. temp = None - ext = os.path.splitext(source_path)[1] + basename, ext = os.path.splitext(source_path) if ext in ('.so', '.dylib') or '.so.' in os.path.basename(source_path) or \ (not ext and is_executable(source_path)): + # Scan and add Unix dependencies. - deps = scan_dependencies(source_path) + if target_path not in IGNORE_UNIX_DEPS_OF: + deps = scan_dependencies(source_path) + else: + deps = [] + for dep in deps: # Only include dependencies with relative path. Otherwise we # end up overwriting system files like /lib/ld-linux.so.2! From 79d8b82cbba85e568e32c15c65f7bab6cfc88931 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 17 Sep 2018 18:56:45 +0200 Subject: [PATCH 272/424] makepanda: fix detection of manylinux platform [skip ci] --- makepanda/makepanda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index ba3a7c35e4..d275cf2119 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -438,7 +438,7 @@ elif target == 'darwin': PLATFORM = 'macosx-{0}.{1}-{2}'.format(osxver[0], osxver[1], arch_tag) -elif target == 'linux' and os.path.isfile("/lib/libc-2.5.so") and os.path.isdir("/opt/python"): +elif target == 'linux' and (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile("/lib64/libc-2.5.so")) and os.path.isdir("/opt/python"): # This is manylinux1. A bit of a sloppy check, though. if GetTargetArch() in ('x86_64', 'amd64'): PLATFORM = 'manylinux1-x86_64' From 3dbdb55782af15bbc9b97defb80c4d30d5fb92b6 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 23 Sep 2018 23:43:57 -0700 Subject: [PATCH 273/424] deploy-ng: Initial NSIS installer support This creates a Windows installer. Still TODO: * Dealing with creating shortcuts * Add license information to installers * Possibly tweak various names (e.g., display names, version numbers, etc.) * Application does not show up in Windows 7 list of programs in the control panel (still has an uninstaller in the start menu) * Add all built binaries to Start Menu --- direct/src/showutil/dist.py | 117 ++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 92ec9d5d85..17cb34c1fc 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1138,6 +1138,105 @@ class bdist_apps(setuptools.Command): with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf: tf.add(build_dir, base_dir, filter=tarfilter) + def create_nsis(self, basename, build_dir, is_64bit): + # Get a list of build applications + build_cmd = self.get_finalized_command('build_apps') + apps = build_cmd.gui_apps.copy() + apps.update(build_cmd.console_apps) + apps = [ + '{}.exe'.format(i) + for i in apps + ] + + fullname = self.distribution.get_fullname() + shortname = self.distribution.get_name() + + # Create the .nsi installer script + nsifile = p3d.Filename(build_cmd.build_base, shortname + ".nsi") + nsifile.unlink() + nsi = open(nsifile.to_os_specific(), "w") + + # Some global info + nsi.write('Name "%s"\n' % shortname) + nsi.write('OutFile "%s"\n' % (fullname+'.exe')) + if is_64bit: + nsi.write('InstallDir "$PROGRAMFILES64\\%s"\n' % shortname) + else: + nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % shortname) + nsi.write('SetCompress auto\n') + nsi.write('SetCompressor lzma\n') + nsi.write('ShowInstDetails nevershow\n') + nsi.write('ShowUninstDetails nevershow\n') + nsi.write('InstType "Typical"\n') + + # Tell Vista that we require admin rights + nsi.write('RequestExecutionLevel admin\n') + nsi.write('\n') + + # TODO offer run and desktop shortcut after we figure out how to deal + # with multiple apps + + nsi.write('!include "MUI2.nsh"\n') + nsi.write('!define MUI_ABORTWARNING\n') + nsi.write('\n') + nsi.write('Var StartMenuFolder\n') + nsi.write('!insertmacro MUI_PAGE_WELCOME\n') + # TODO license file + nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n') + nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n') + nsi.write('!insertmacro MUI_PAGE_INSTFILES\n') + nsi.write('!insertmacro MUI_PAGE_FINISH\n') + nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n') + nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n') + nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n') + nsi.write('!insertmacro MUI_UNPAGE_FINISH\n') + nsi.write('!insertmacro MUI_LANGUAGE "English"\n') + + # This section defines the installer. + nsi.write('Section "" SecCore\n') + nsi.write(' SetOutPath "$INSTDIR"\n') + curdir = "" + for root, dirs, files in os.walk(build_dir): + for name in files: + basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name)) + file = p3d.Filename(basefile) + file.makeAbsolute() + file.makeRelativeTo(build_dir) + outdir = file.getDirname().replace('/', '\\') + if curdir != outdir: + nsi.write(' SetOutPath "$INSTDIR\\%s"\n' % outdir) + curdir = outdir + nsi.write(' File "%s"\n' % (basefile.toOsSpecific())) + nsi.write(' SetOutPath "$INSTDIR"\n') + nsi.write(' WriteUninstaller "$INSTDIR\\Uninstall.exe"\n') + nsi.write(' ; Start menu items\n') + nsi.write(' !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n') + nsi.write(' CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n') + for app in apps: + nsi.write(' CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s"\n' % (shortname, app)) + nsi.write(' CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n') + nsi.write(' !insertmacro MUI_STARTMENU_WRITE_END\n') + nsi.write('SectionEnd\n') + + # This section defines the uninstaller. + nsi.write('Section Uninstall\n') + nsi.write(' RMDir /r "$INSTDIR"\n') + nsi.write(' ; Desktop icon\n') + nsi.write(' Delete "$DESKTOP\\%s.lnk"\n' % shortname) + nsi.write(' ; Start menu items\n') + nsi.write(' !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n') + nsi.write(' RMDir /r "$SMPROGRAMS\\$StartMenuFolder"\n') + nsi.write('SectionEnd\n') + nsi.close() + + cmd = ['makensis'] + for flag in ["V2"]: + cmd.append( + '{}{}'.format('/' if sys.platform.startswith('win') else '-', flag) + ) + cmd.append(nsifile.to_os_specific()) + subprocess.check_call(cmd) + def run(self): build_cmd = self.get_finalized_command('build_apps') if not build_cmd.platforms: @@ -1165,5 +1264,23 @@ class bdist_apps(setuptools.Command): compress = 'bz2' self.create_tarball(basename, build_dir, compress) + elif installer == 'nsis': + if not platform.startswith('win'): + self.announce( + '\tNSIS installer not supported for platform: {}'.format(platform), + distutils.log.ERROR + ) + continue + try: + subprocess.call(['makensis', '--version']) + except OSError: + self.announce( + '\tCould not find makensis tool that is required to build NSIS installers', + distutils.log.ERROR + ) + # continue + is_64bit = platform == 'win_amd64' + self.create_nsis(basename, build_dir, is_64bit) + else: self.announce('\tUnknown installer: {}'.format(installer), distutils.log.ERROR) From 9b85a288616be32dfbb0060b8ce9b435c6beff6d Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Sep 2018 16:53:00 +0200 Subject: [PATCH 274/424] putil: rename MouseData to PointerData to be more inclusive --- panda/src/device/inputDevice.h | 4 +- panda/src/display/mouseAndKeyboard.cxx | 2 +- panda/src/putil/mouseData.h | 38 +++----------- panda/src/putil/p3putil_composite2.cxx | 2 +- .../src/putil/{mouseData.I => pointerData.I} | 22 ++++---- .../putil/{mouseData.cxx => pointerData.cxx} | 10 ++-- panda/src/putil/pointerData.h | 51 +++++++++++++++++++ panda/src/x11display/x11GraphicsWindow.cxx | 4 +- 8 files changed, 79 insertions(+), 54 deletions(-) rename panda/src/putil/{mouseData.I => pointerData.I} (68%) rename panda/src/putil/{mouseData.cxx => pointerData.cxx} (70%) create mode 100644 panda/src/putil/pointerData.h diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 7e4d4f623b..10ad2c8177 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -20,7 +20,7 @@ #include "buttonEventList.h" #include "pointerEvent.h" #include "pointerEventList.h" -#include "mouseData.h" +#include "pointerData.h" #include "trackerData.h" #include "clockObject.h" @@ -29,8 +29,6 @@ #include "lightMutex.h" #include "lightMutexHolder.h" -typedef MouseData PointerData; - /** * This is a structure representing a single input device. Input devices may * have zero or more buttons, pointers, or axes associated with them, and diff --git a/panda/src/display/mouseAndKeyboard.cxx b/panda/src/display/mouseAndKeyboard.cxx index 8243ef4759..97c6447bdb 100644 --- a/panda/src/display/mouseAndKeyboard.cxx +++ b/panda/src/display/mouseAndKeyboard.cxx @@ -99,7 +99,7 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, output.set_data(_pixel_size_output, EventParameter(_pixel_size)); if (device->has_pointer()) { - const MouseData &mdata = device->get_pointer(); + PointerData mdata = device->get_pointer(); if (mdata._in_window) { // Get mouse motion in pixels. diff --git a/panda/src/putil/mouseData.h b/panda/src/putil/mouseData.h index 0d2a294568..2747c26061 100644 --- a/panda/src/putil/mouseData.h +++ b/panda/src/putil/mouseData.h @@ -7,45 +7,21 @@ * with this source code in a file named "LICENSE." * * @file mouseData.h - * @author drose - * @date 1999-02-08 + * @author rdb + * @date 2018-09-24 */ #ifndef MOUSEDATA_H #define MOUSEDATA_H -#include "pandabase.h" - -#include "modifierButtons.h" +#include "pointerData.h" +BEGIN_PUBLISH /** - * Holds the data that might be generated by a 2-d pointer input device, such - * as the mouse in the GraphicsWindow. + * Deprecated alias for PointerData. */ -class EXPCL_PANDA_PUTIL MouseData { -PUBLISHED: - INLINE MouseData(); - INLINE MouseData(const MouseData ©); - INLINE void operator = (const MouseData ©); +typedef PointerData MouseData; - INLINE double get_x() const; - INLINE double get_y() const; - INLINE bool get_in_window() const; - - void output(std::ostream &out) const; - - MAKE_PROPERTY(x, get_x); - MAKE_PROPERTY(y, get_y); - MAKE_PROPERTY(in_window, get_in_window); - -public: - bool _in_window; - double _xpos; - double _ypos; -}; - -INLINE std::ostream &operator << (std::ostream &out, const MouseData &md); - -#include "mouseData.I" +END_PUBLISH #endif diff --git a/panda/src/putil/p3putil_composite2.cxx b/panda/src/putil/p3putil_composite2.cxx index 9b0f01d851..80ae7450bb 100644 --- a/panda/src/putil/p3putil_composite2.cxx +++ b/panda/src/putil/p3putil_composite2.cxx @@ -9,11 +9,11 @@ #include "loaderOptions.cxx" #include "modifierButtons.cxx" #include "mouseButton.cxx" -#include "mouseData.cxx" #include "nameUniquifier.cxx" #include "nodeCachedReferenceCount.cxx" #include "paramValue.cxx" #include "pbitops.cxx" +#include "pointerData.cxx" #include "pta_ushort.cxx" #include "simpleHashMap.cxx" #include "sparseArray.cxx" diff --git a/panda/src/putil/mouseData.I b/panda/src/putil/pointerData.I similarity index 68% rename from panda/src/putil/mouseData.I rename to panda/src/putil/pointerData.I index c0ab6997ef..6697c31d1a 100644 --- a/panda/src/putil/mouseData.I +++ b/panda/src/putil/pointerData.I @@ -6,7 +6,7 @@ * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * - * @file mouseData.I + * @file pointerData.I * @author drose * @date 2002-07-15 */ @@ -14,8 +14,8 @@ /** * */ -INLINE MouseData:: -MouseData() { +INLINE PointerData:: +PointerData() { _in_window = false; _xpos = 0; _ypos = 0; @@ -24,8 +24,8 @@ MouseData() { /** * */ -INLINE MouseData:: -MouseData(const MouseData ©) : +INLINE PointerData:: +PointerData(const PointerData ©) : _in_window(copy._in_window), _xpos(copy._xpos), _ypos(copy._ypos) @@ -35,8 +35,8 @@ MouseData(const MouseData ©) : /** * */ -INLINE void MouseData:: -operator = (const MouseData ©) { +INLINE void PointerData:: +operator = (const PointerData ©) { _in_window = copy._in_window; _xpos = copy._xpos; _ypos = copy._ypos; @@ -45,7 +45,7 @@ operator = (const MouseData ©) { /** * */ -INLINE double MouseData:: +INLINE double PointerData:: get_x() const { return _xpos; } @@ -53,7 +53,7 @@ get_x() const { /** * */ -INLINE double MouseData:: +INLINE double PointerData:: get_y() const { return _ypos; } @@ -61,13 +61,13 @@ get_y() const { /** * */ -INLINE bool MouseData:: +INLINE bool PointerData:: get_in_window() const { return _in_window; } -INLINE std::ostream &operator << (std::ostream &out, const MouseData &md) { +INLINE std::ostream &operator << (std::ostream &out, const PointerData &md) { md.output(out); return out; } diff --git a/panda/src/putil/mouseData.cxx b/panda/src/putil/pointerData.cxx similarity index 70% rename from panda/src/putil/mouseData.cxx rename to panda/src/putil/pointerData.cxx index de83bd1505..ccf8e9d9f9 100644 --- a/panda/src/putil/mouseData.cxx +++ b/panda/src/putil/pointerData.cxx @@ -6,21 +6,21 @@ * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * - * @file mouseData.cxx + * @file pointerData.cxx * @author drose * @date 1999-02-08 */ -#include "mouseData.h" +#include "pointerData.h" /** * */ -void MouseData:: +void PointerData:: output(std::ostream &out) const { if (!_in_window) { - out << "MouseData: Not in window"; + out << "PointerData: Not in window"; } else { - out << "MouseData: (" << _xpos << ", " << _ypos << ")"; + out << "PointerData: (" << _xpos << ", " << _ypos << ")"; } } diff --git a/panda/src/putil/pointerData.h b/panda/src/putil/pointerData.h new file mode 100644 index 0000000000..aa3f3aeb62 --- /dev/null +++ b/panda/src/putil/pointerData.h @@ -0,0 +1,51 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file pointerData.h + * @author drose + * @date 1999-02-08 + */ + +#ifndef POINTERDATA_H +#define POINTERDATA_H + +#include "pandabase.h" + +#include "modifierButtons.h" + +/** + * Holds the data that might be generated by a 2-d pointer input device, such + * as the mouse in the GraphicsWindow. + */ +class EXPCL_PANDA_PUTIL PointerData { +PUBLISHED: + INLINE PointerData(); + INLINE PointerData(const PointerData ©); + INLINE void operator = (const PointerData ©); + + INLINE double get_x() const; + INLINE double get_y() const; + INLINE bool get_in_window() const; + + void output(std::ostream &out) const; + + MAKE_PROPERTY(x, get_x); + MAKE_PROPERTY(y, get_y); + MAKE_PROPERTY(in_window, get_in_window); + +public: + bool _in_window; + double _xpos; + double _ypos; +}; + +INLINE std::ostream &operator << (std::ostream &out, const PointerData &md); + +#include "pointerData.I" + +#endif diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index b9fa562c64..6297e38758 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -354,7 +354,7 @@ process_events() { case MotionNotify: if (_dga_mouse_enabled) { - const MouseData &md = _input->get_pointer(); + PointerData md = _input->get_pointer(); _input->set_pointer_in_window(md.get_x() + event.xmotion.x_root, md.get_y() + event.xmotion.y_root); } else { _input->set_pointer_in_window(event.xmotion.x, event.xmotion.y); @@ -376,7 +376,7 @@ process_events() { case EnterNotify: if (_dga_mouse_enabled) { - const MouseData &md = _input->get_pointer(); + PointerData md = _input->get_pointer(); _input->set_pointer_in_window(md.get_x(), md.get_y()); } else { _input->set_pointer_in_window(event.xcrossing.x, event.xcrossing.y); From 85752b06f5614d67f20754801a9f96b466d8fa7d Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Sep 2018 17:20:50 +0200 Subject: [PATCH 275/424] device: delete InputDevice copy ctor / assign operator --- panda/src/device/inputDevice.cxx | 16 ------------- panda/src/device/inputDevice.h | 6 ++--- .../src/display/graphicsWindowInputDevice.cxx | 23 ------------------- panda/src/display/graphicsWindowInputDevice.h | 3 --- 4 files changed, 3 insertions(+), 45 deletions(-) diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index aedfeb8222..742c31b688 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -37,22 +37,6 @@ InputDevice(const std::string &name, DeviceClass dev_class, int flags) : _button_events = new ButtonEventList; } -/** - * - */ -InputDevice:: -InputDevice(const InputDevice ©) { - nassertv(false); -} - -/** - * - */ -void InputDevice:: -operator = (const InputDevice ©) { - nassertv(false); -} - /** * */ diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 10ad2c8177..24b27186e3 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -165,9 +165,9 @@ protected: public: InputDevice(); - InputDevice(const InputDevice ©); - void operator = (const InputDevice ©); - ~InputDevice(); + InputDevice(const InputDevice ©) = delete; + InputDevice &operator = (const InputDevice ©) = delete; + virtual ~InputDevice(); INLINE std::string get_name() const; INLINE std::string get_manufacturer() const; diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index e967216f03..3d7102f06f 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -62,29 +62,6 @@ pointer_and_keyboard(GraphicsWindow *host, const string &name) { GraphicsWindowInputDevice(host, name, IDF_has_pointer | IDF_has_keyboard); } -/** - * - */ -GraphicsWindowInputDevice:: -GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©) { - *this = copy; -} - -/** - * - */ -void GraphicsWindowInputDevice:: -operator = (const GraphicsWindowInputDevice ©) { - InputDevice::operator = (copy); -} - -/** - * - */ -GraphicsWindowInputDevice:: -~GraphicsWindowInputDevice() { -} - /** * Records that the indicated button has been depressed. */ diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index 1573618723..1f9d941add 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -35,9 +35,6 @@ public: static PT(GraphicsWindowInputDevice) pointer_and_keyboard(GraphicsWindow *host, const std::string &name); INLINE GraphicsWindowInputDevice(); - GraphicsWindowInputDevice(const GraphicsWindowInputDevice ©); - void operator = (const GraphicsWindowInputDevice ©); - ~GraphicsWindowInputDevice(); PUBLISHED: // The following interface is for the various kinds of GraphicsWindows to From c4b3b558c92f7a30eb060f7d943d787919ab6c4d Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Sep 2018 20:48:50 +0200 Subject: [PATCH 276/424] samples: fixes and improvements to gamepad and mappingGUI sample --- samples/gamepad/gamepad.py | 194 ++++++++----- samples/gamepad/mappingGUI.py | 345 ++++++++++++---------- samples/gamepad/models/xbone-icons.egg | 384 +++++++++++++++++++++++++ samples/gamepad/models/xbone-icons.png | Bin 0 -> 29275 bytes 4 files changed, 714 insertions(+), 209 deletions(-) create mode 100644 samples/gamepad/models/xbone-icons.egg create mode 100644 samples/gamepad/models/xbone-icons.png diff --git a/samples/gamepad/gamepad.py b/samples/gamepad/gamepad.py index 82154838f6..c684c01253 100644 --- a/samples/gamepad/gamepad.py +++ b/samples/gamepad/gamepad.py @@ -9,21 +9,51 @@ move the camera where the right stick will rotate the camera. from direct.showbase.ShowBase import ShowBase from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 +from panda3d.core import TextPropertiesManager from direct.gui.OnscreenText import OnscreenText -loadPrcFileData("", "notify-level-device debug") +loadPrcFileData("", """ + default-fov 60 + notify-level-device debug +""") + +# Informational text in the bottom-left corner. We use the special \5 +# character to embed an image representing the gamepad buttons. +INFO_TEXT = """Move \5lstick\5 to strafe, \5rstick\5 to turn +Press \5ltrigger\5 and \5rtrigger\5 to go down/up +Press \5face_x\5 to reset camera""" + class App(ShowBase): def __init__(self): ShowBase.__init__(self) - # print all events sent through the messenger - self.messenger.toggleVerbose() + # Print all events sent through the messenger + #self.messenger.toggleVerbose() + + # Load the graphics for the gamepad buttons and register them, so that + # we can embed them in our information text. + graphics = loader.loadModel("models/xbone-icons.egg") + mgr = TextPropertiesManager.getGlobalPtr() + for name in ["face_a", "face_b", "face_x", "face_y", "ltrigger", "rtrigger", "lstick", "rstick"]: + graphic = graphics.find("**/" + name) + graphic.setScale(1.5) + mgr.setGraphic(name, graphic) + graphic.setZ(-0.5) + + # Show the informational text in the corner. + self.lblInfo = OnscreenText( + parent = self.a2dBottomLeft, + pos = (0.1, 0.3), + fg = (1, 1, 1, 1), + bg = (0.2, 0.2, 0.2, 0.9), + align = TextNode.A_left, + text = INFO_TEXT) + self.lblInfo.textNode.setCardAsMargin(0.5, 0.5, 0.5, 0.2) self.lblWarning = OnscreenText( text = "No devices found", fg=(1,0,0,1), scale = .25) - self.lblWarning.hide() self.lblAction = OnscreenText( text = "Action", @@ -31,99 +61,133 @@ class App(ShowBase): scale = .15) self.lblAction.hide() - self.checkDevices() + # Is there a gamepad connected? + self.gamepad = None + devices = self.devices.getDevices(InputDevice.DC_gamepad) + if devices: + self.connect(devices[0]) # Accept device dis-/connection events - # NOTE: catching the events here will overwrite the accept in showbase, hence - # we need to forward the event in the functions we set here! self.accept("connect-device", self.connect) self.accept("disconnect-device", self.disconnect) self.accept("escape", exit) - self.accept("gamepad0-start", exit) - self.accept("flight_stick0-start", exit) # Accept button events of the first connected gamepad - self.accept("gamepad0-action_a", self.doAction, extraArgs=[True, "Action"]) - self.accept("gamepad0-action_a-up", self.doAction, extraArgs=[False, "Release"]) - self.accept("gamepad0-action_b", self.doAction, extraArgs=[True, "Action 2"]) - self.accept("gamepad0-action_b-up", self.doAction, extraArgs=[False, "Release"]) + self.accept("gamepad-back", exit) + self.accept("gamepad-start", exit) + self.accept("gamepad-face_x", self.reset) + self.accept("gamepad-face_a", self.action, extraArgs=["face_a"]) + self.accept("gamepad-face_a-up", self.actionUp) + self.accept("gamepad-face_b", self.action, extraArgs=["face_b"]) + self.accept("gamepad-face_b-up", self.actionUp) + self.accept("gamepad-face_y", self.action, extraArgs=["face_y"]) + self.accept("gamepad-face_y-up", self.actionUp) self.environment = loader.loadModel("environment") self.environment.reparentTo(render) - # disable pandas default mouse-camera controls so we can handle the camera - # movements by ourself + # Disable the default mouse-camera controls since we need to handle + # our own camera controls. self.disableMouse() - - # list of connected gamepad devices - gamepads = base.devices.getDevices(InputDevice.DC_gamepad) - - # set the center position of the control sticks - # NOTE: here we assume, that the wheel is centered when the application get started. - # In real world applications, you should notice the user and give him enough time - # to center the wheel until you store the center position of the controler! - self.lxcenter = gamepads[0].findControl(InputDevice.C_left_x).state - self.lycenter = gamepads[0].findControl(InputDevice.C_left_y).state - self.rxcenter = gamepads[0].findControl(InputDevice.C_right_x).state - self.rycenter = gamepads[0].findControl(InputDevice.C_right_y).state - + self.reset() self.taskMgr.add(self.moveTask, "movement update task") def connect(self, device): - # we need to forward the event to the connectDevice function of showbase - self.connectDevice(device) - # Now we can check for ourself - self.checkDevices() + """Event handler that is called when a device is discovered.""" + + # We're only interested if this is a gamepad and we don't have a + # gamepad yet. + if device.device_class == InputDevice.DC_gamepad and not self.gamepad: + print("Found %s" % (device)) + self.gamepad = device + + # Enable this device to ShowBase so that we can receive events. + # We set up the events with a prefix of "gamepad-". + self.attachInputDevice(device, prefix="gamepad") + + # Hide the warning that we have no devices. + self.lblWarning.hide() def disconnect(self, device): - # we need to forward the event to the disconnectDevice function of showbase - self.disconnectDevice(device) - # Now we can check for ourself - self.checkDevices() + """Event handler that is called when a device is removed.""" - def checkDevices(self): - # check if we have gamepad devices connected - if self.devices.get_devices(InputDevice.DC_gamepad): - # we have at least one gamepad device - self.lblWarning.hide() + if self.gamepad != device: + # We don't care since it's not our gamepad. + return + + # Tell ShowBase that the device is no longer needed. + print("Disconnected %s" % (device)) + self.detachInputDevice(device) + self.gamepad = None + + # Do we have any other gamepads? Attach the first other gamepad. + devices = self.devices.getDevices(InputDevice.DC_gamepad) + if devices: + self.connect(devices[0]) else: - # no devices connected + # No devices. Show the warning. self.lblWarning.show() - def doAction(self, showText, text): - if showText and self.lblAction.isHidden(): - self.lblAction.show() - else: - self.lblAction.hide() + def reset(self): + """Reset the camera to the initial position.""" + + self.camera.setPosHpr(0, -200, 10, 0, 0, 0) + + def action(self, button): + # Just show which button has been pressed. + self.lblAction.text = "Pressed \5%s\5" % button + self.lblAction.show() + + def actionUp(self): + # Hide the label showing which button is pressed. + self.lblAction.hide() def moveTask(self, task): dt = globalClock.getDt() - movementVec = Vec3() - gamepads = base.devices.getDevices(InputDevice.DC_gamepad) - if len(gamepads) == 0: - # savety check + if not self.gamepad: return task.cont + strafe_speed = 85 + vert_speed = 50 + turn_speed = 100 + + # If the left stick is pressed, we will go faster. + lstick = self.gamepad.findButton("lstick") + if lstick.pressed: + strafe_speed *= 2.0 + # we will use the first found gamepad # Move the camera left/right - left_x = gamepads[0].findControl(InputDevice.C_left_x) - movementVec.setX(left_x.state - self.lxcenter) - # Move the camera forward/backward - left_y = gamepads[0].findControl(InputDevice.C_left_y) - movementVec.setY(left_y.state - self.lycenter) - # Control the cameras heading - right_x = gamepads[0].findControl(InputDevice.C_right_x) - base.camera.setH(base.camera, 100 * dt * (right_x.state - self.rxcenter)) - # Control the cameras pitch - right_y = gamepads[0].findControl(InputDevice.C_right_y) - base.camera.setP(base.camera, 100 * dt * (right_y.state - self.rycenter)) + strafe = Vec3(0) + left_x = self.gamepad.findAxis(InputDevice.Axis.left_x) + left_y = self.gamepad.findAxis(InputDevice.Axis.left_y) + strafe.x = left_x.value + strafe.y = left_y.value - # calculate movement - base.camera.setX(base.camera, 100 * dt * movementVec.getX()) - base.camera.setY(base.camera, 100 * dt * movementVec.getY()) + # Apply some deadzone, since the sticks don't center exactly at 0 + if strafe.lengthSquared() >= 0.01: + self.camera.setPos(self.camera, strafe * strafe_speed * dt) + + # Use the triggers for the vertical position. + trigger_l = self.gamepad.findAxis(InputDevice.Axis.left_trigger) + trigger_r = self.gamepad.findAxis(InputDevice.Axis.right_trigger) + lift = trigger_r.value - trigger_l.value + self.camera.setZ(self.camera.getZ() + (lift * vert_speed * dt)) + + # Move the camera forward/backward + right_x = self.gamepad.findAxis(InputDevice.Axis.right_x) + right_y = self.gamepad.findAxis(InputDevice.Axis.right_y) + + # Again, some deadzone + if abs(right_x.value) >= 0.1 or abs(right_y.value) >= 0.1: + self.camera.setH(self.camera, turn_speed * dt * -right_x.value) + self.camera.setP(self.camera, turn_speed * dt * right_y.value) + + # Reset the roll so that the camera remains upright. + self.camera.setR(0) return task.cont diff --git a/samples/gamepad/mappingGUI.py b/samples/gamepad/mappingGUI.py index ce9234bd1d..84f26a9f60 100644 --- a/samples/gamepad/mappingGUI.py +++ b/samples/gamepad/mappingGUI.py @@ -16,62 +16,75 @@ from panda3d.core import ( TextNode, Vec2, InputDevice, - loadPrcFileData) + loadPrcFileData, + GamepadButton, + KeyboardButton) # Make sure the textures look crisp on every device that supports # non-power-2 textures loadPrcFileData("", "textures-auto-power-2 #t") -class App(ShowBase): +# How much an axis should have moved for it to register as a movement. +DEAD_ZONE = 0.33 + + +class InputMapping(object): + """A container class for storing a mapping from a string action to either + an axis or a button. You could extend this with additional methods to load + the default mappings from a configuration file. """ + + # Define all the possible actions. + actions = ( + "Move forward", "Move backward", "Move left", "Move right", "Jump", + "Buy", "Use", "Break", "Fix", "Trash", "Change", "Mail", "Upgrade", + ) + def __init__(self): - ShowBase.__init__(self) + self.__map = dict.fromkeys(self.actions) - self.setBackgroundColor(0, 0, 0) - # make the font look nice at a big scale - DGG.getDefaultFont().setPixelsPerUnit(100) + def mapButton(self, action, button): + self.__map[action] = ("button", str(button)) - # a dict of actions and button/axis events - self.gamepadMapping = { - "Move forward":"Left Stick Y", - "Move backward":"Left Stick Y", - "Move left":"Left Stick X", - "Move right":"Left Stick X", - "Jump":"a", - "Action":"b", - "Sprint":"x", - "Map":"y", - "action-1":"c", - "action-2":"d", - "action-3":"e", - "action-4":"f", - "action-5":"g", - "action-6":"h", - "action-7":"i", - "action-8":"j", - "action-9":"k", - "action-10":"l", - "action-11":"m", - } - # this will store the action that we want to remap - self.actionToMap = "" - # this will store the key/axis that we want to asign to an action - self.newActionKey = "" - # this will store the label that needs to be actualized in the list - self.actualizeLabel = None + def mapAxis(self, action, axis): + self.__map[action] = ("axis", axis.name) - # The geometry for our basic buttons - maps = loader.loadModel("models/button_map") - self.buttonGeom = ( - maps.find("**/ready"), - maps.find("**/click"), - maps.find("**/hover"), - maps.find("**/disabled")) + def unmap(self): + self.__map[action] = None - # Create the dialog that asks the user for input on a given - # action to map a key to. - DGG.setDefaultDialogGeom("models/dialog.png") - # setup a dialog to ask for device input - self.dlgInput = OkCancelDialog( + def formatMapping(self, action): + """Returns a string label describing the mapping for a given action, + for displaying in a GUI. """ + mapping = self.__map.get(action) + if not mapping: + return "Unmapped" + + # Format the symbolic string from Panda nicely. In a real-world game, + # you might want to look these up in a translation table, or so. + label = mapping[1].replace('_', ' ').title() + if mapping[0] == "axis": + return "Axis: " + label + else: + return "Button: " + label + + +class ChangeActionDialog(object): + """Encapsulates the UI dialog that opens up when changing a mapping. It + holds the state of which action is being set and which button is pressed + and invokes a callback function when the dialog is exited.""" + + def __init__(self, action, button_geom, command): + # This stores which action we are remapping. + self.action = action + + # This will store the key/axis that we want to asign to an action + self.newInputType = "" + self.newInput = "" + self.setKeyCalled = False + + self.__command = command + + # Initialize the DirectGUI stuff. + self.dialog = OkCancelDialog( dialogName="dlg_device_input", pos=(0, 0, 0.25), text="Hit desired key:", @@ -82,7 +95,7 @@ class App(ShowBase): text_align=TextNode.ACenter, fadeScreen=0.65, frameColor=VBase4(0.3, 0.3, 0.3, 1), - button_geom=self.buttonGeom, + button_geom=button_geom, button_scale=0.15, button_text_scale=0.35, button_text_align=TextNode.ALeft, @@ -93,13 +106,73 @@ class App(ShowBase): button_frameColor=VBase4(0, 0, 0, 0), button_frameSize=VBase4(-1.0, 1.0, -0.25, 0.25), button_pressEffect=False, - command=self.closeDialog) - self.dlgInput.setTransparency(True) - self.dlgInput.configureDialog() - scale = self.dlgInput["image_scale"] - self.dlgInput["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0) - self.dlgInput["text_pos"] = (self.dlgInput["text_pos"][0], self.dlgInput["text_pos"][1] + 0.06) - self.dlgInput.hide() + command=self.onClose) + self.dialog.setTransparency(True) + self.dialog.configureDialog() + scale = self.dialog["image_scale"] + self.dialog["image_scale"] = (scale[0]/2.0, scale[1], scale[2]/2.0) + self.dialog["text_pos"] = (self.dialog["text_pos"][0], self.dialog["text_pos"][1] + 0.06) + + def buttonPressed(self, button): + if any(button.guiItem.getState() == 1 for button in self.dialog.buttonList): + # Ignore events while any of the dialog buttons are active, because + # otherwise we register mouse clicks when the user is trying to + # exit the dialog. + return + + text = str(button).replace('_', ' ').title() + self.dialog["text"] = "New event will be:\n\nButton: " + text + self.newInputType = "button" + self.newInput = button + + def axisMoved(self, axis): + text = axis.name.replace('_', ' ').title() + self.dialog["text"] = "New event will be:\n\nAxis: " + text + self.newInputType = "axis" + self.newInput = axis + + def onClose(self, result): + """Called when the OK or Cancel button is pressed.""" + self.dialog.cleanup() + + # Call the constructor-supplied callback with our new setting, if any. + if self.newInput and result == DGG.DIALOG_OK: + self.__command(self.action, self.newInputType, self.newInput) + else: + # Cancel (or no input was pressed) + self.__command(self.action, None, None) + + +class MappingGUIDemo(ShowBase): + def __init__(self): + ShowBase.__init__(self) + + self.setBackgroundColor(0, 0, 0) + # make the font look nice at a big scale + DGG.getDefaultFont().setPixelsPerUnit(100) + + # Store our mapping, with some sensible defaults. In a real game, you + # will want to load these from a configuration file. + self.mapping = InputMapping() + self.mapping.mapAxis("Move forward", InputDevice.Axis.left_y) + self.mapping.mapAxis("Move backward", InputDevice.Axis.left_y) + self.mapping.mapAxis("Move left", InputDevice.Axis.left_x) + self.mapping.mapAxis("Move right", InputDevice.Axis.left_x) + self.mapping.mapButton("Jump", GamepadButton.face_a()) + self.mapping.mapButton("Use", GamepadButton.face_b()) + self.mapping.mapButton("Break", GamepadButton.face_x()) + self.mapping.mapButton("Fix", GamepadButton.face_y()) + + # The geometry for our basic buttons + maps = loader.loadModel("models/button_map") + self.buttonGeom = ( + maps.find("**/ready"), + maps.find("**/click"), + maps.find("**/hover"), + maps.find("**/disabled")) + + # Change the default dialog skin. + DGG.setDefaultDialogGeom("models/dialog.png") # create a sample title self.textscale = 0.1 @@ -135,6 +208,7 @@ class App(ShowBase): decMaps.find("**/dec_click"), decMaps.find("**/dec_hover"), decMaps.find("**/dec_disabled")) + # create the scrolled frame that will hold our list self.lstActionMap = DirectScrolledFrame( # make the frame occupy the whole window @@ -167,135 +241,116 @@ class App(ShowBase): idx = 0 self.listBGEven = base.loader.loadModel("models/list_item_even") self.listBGOdd = base.loader.loadModel("models/list_item_odd") - for key, value in self.gamepadMapping.items(): - item = self.__makeListItem(key, key, value, idx) + self.actionLabels = {} + for action in self.mapping.actions: + mapped = self.mapping.formatMapping(action) + item = self.__makeListItem(action, mapped, idx) item.reparentTo(self.lstActionMap.getCanvas()) idx += 1 # recalculate the canvas size to set scrollbars if necesary self.lstActionMap["canvasSize"] = ( base.a2dLeft+0.05, base.a2dRight-0.05, - -(len(self.gamepadMapping.keys())*0.1), 0.09) + -(len(self.mapping.actions)*0.1), 0.09) self.lstActionMap.setCanvasSize() - def closeDialog(self, result): - self.dlgInput.hide() - if result == DGG.DIALOG_OK: + def closeDialog(self, action, newInputType, newInput): + """Called in callback when the dialog is closed. newInputType will be + "button" or "axis", or None if the remapping was cancelled.""" + + self.dlgInput = None + + if newInputType is not None: # map the event to the given action - self.gamepadMapping[self.actionToMap] = self.newActionKey + if newInputType == "axis": + self.mapping.mapAxis(action, newInput) + else: + self.mapping.mapButton(action, newInput) + # actualize the label in the list that shows the current # event for the action - self.actualizeLabel["text"] = self.newActionKey + self.actionLabels[action]["text"] = self.mapping.formatMapping(action) # cleanup - self.dlgInput["text"] ="Hit desired key:" - self.actionToMap = "" - self.newActionKey = "" - self.actualizeLabel = None for bt in base.buttonThrowers: + bt.node().setSpecificFlag(True) bt.node().setButtonDownEvent("") for bt in base.deviceButtonThrowers: + bt.node().setSpecificFlag(True) bt.node().setButtonDownEvent("") taskMgr.remove("checkControls") - def changeMapping(self, action, label): - # set the action that we want to map a new key to - self.actionToMap = action - # set the label that needs to be actualized - self.actualizeLabel = label - # show our dialog - self.dlgInput.show() + # Now detach all the input devices. + for device in self.attachedDevices: + base.detachInputDevice(device) + self.attachedDevices.clear() - # catch all button events + def changeMapping(self, action): + # Create the dialog window + self.dlgInput = ChangeActionDialog(action, button_geom=self.buttonGeom, command=self.closeDialog) + + # Attach all input devices. + devices = base.devices.getDevices() + for device in devices: + base.attachInputDevice(device) + self.attachedDevices = devices + + # Disable regular button events on all button event throwers, and + # instead broadcast a generic event. for bt in base.buttonThrowers: + bt.node().setSpecificFlag(False) bt.node().setButtonDownEvent("keyListenEvent") for bt in base.deviceButtonThrowers: + bt.node().setSpecificFlag(False) bt.node().setButtonDownEvent("deviceListenEvent") - self.setKeyCalled = False - self.accept("keyListenEvent", self.setKey) - self.accept("deviceListenEvent", self.setDeviceKey) - # As there are no events thrown for control changes, we set up - # a task to check if the controls got moved - # This list will help us for checking which controls got moved - self.controlStates = {None:{}} + self.accept("keyListenEvent", self.dlgInput.buttonPressed) + self.accept("deviceListenEvent", self.dlgInput.buttonPressed) + + # As there are no events thrown for control changes, we set up a task + # to check if the controls were moved + # This list will help us for checking which controls were moved + self.axisStates = {None: {}} # fill it with all available controls - for device in base.devices.get_devices(): - for ctrl in device.controls: - if device not in self.controlStates.keys(): - self.controlStates.update({device: {ctrl.axis: ctrl.state}}) + for device in devices: + for axis in device.axes: + if device not in self.axisStates.keys(): + self.axisStates.update({device: {axis.axis: axis.value}}) else: - self.controlStates[device].update({ctrl.axis: ctrl.state}) + self.axisStates[device].update({axis.axis: axis.value}) # start the task taskMgr.add(self.watchControls, "checkControls") def watchControls(self, task): # move through all devices and all it's controls - for device in base.devices.get_devices(): - for ctrl in device.controls: - # if a control got changed more than the given puffer zone - if self.controlStates[device][ctrl.axis] + 0.2 < ctrl.state or \ - self.controlStates[device][ctrl.axis] - 0.2 > ctrl.state: + for device in self.attachedDevices: + if device.device_class == InputDevice.DC_mouse: + # Ignore mouse axis movement, or the user can't even navigate + # to the OK/Cancel buttons! + continue + + for axis in device.axes: + # if a control got changed more than the given dead zone + if self.axisStates[device][axis.axis] + DEAD_ZONE < axis.value or \ + self.axisStates[device][axis.axis] - DEAD_ZONE > axis.value: # set the current state in the dict - self.controlStates[device][ctrl.axis] = ctrl.state - # check which axis got moved - if ctrl.axis == InputDevice.C_left_x: - self.setKey("Left Stick X") - elif ctrl.axis == InputDevice.C_left_y: - self.setKey("Left Stick Y") - elif ctrl.axis == InputDevice.C_left_trigger: - self.setKey("Left Trigger") - elif ctrl.axis == InputDevice.C_right_x: - self.setKey("Right Stick X") - elif ctrl.axis == InputDevice.C_right_y: - self.setKey("Right Stick Y") - elif ctrl.axis == InputDevice.C_right_trigger: - self.setKey("Right Trigger") - elif ctrl.axis == InputDevice.C_x: - self.setKey("X") - elif ctrl.axis == InputDevice.C_y: - self.setKey("Y") - elif ctrl.axis == InputDevice.C_trigger: - self.setKey("Trigger") - elif ctrl.axis == InputDevice.C_throttle: - self.setKey("Throttle") - elif ctrl.axis == InputDevice.C_rudder: - self.setKey("Rudder") - elif ctrl.axis == InputDevice.C_hat_x: - self.setKey("Hat X") - elif ctrl.axis == InputDevice.C_hat_y: - self.setKey("Hat Y") - elif ctrl.axis == InputDevice.C_wheel: - self.setKey("Wheel") - elif ctrl.axis == InputDevice.C_accelerator: - self.setKey("Acclerator") - elif ctrl.axis == InputDevice.C_brake: - self.setKey("Break") + self.axisStates[device][axis.axis] = axis.value + + # Format the axis for being displayed. + if axis.axis != InputDevice.Axis.none: + #label = axis.axis.name.replace('_', ' ').title() + self.dlgInput.axisMoved(axis.axis) + return task.cont - def setKey(self, args): - self.setKeyCalled = True - if self.dlgInput.buttonList[0].guiItem.getState() == 1: - # this occurs if the OK button was clicked. To prevent to - # always set the mouse1 event whenever the OK button was - # pressed, we instantly return from this function - return - self.dlgInput["text"] = "New event will be:\n\n" + args - self.newActionKey = args - - def setDeviceKey(self, args): - if not self.setKeyCalled: - self.setKey(args) - self.setKeyCalled = False - - def __makeListItem(self, itemName, action, event, index): + def __makeListItem(self, action, event, index): def dummy(): pass if index % 2 == 0: bg = self.listBGEven else: bg = self.listBGOdd item = DirectFrame( - text=itemName, + text=action, geom=bg, geom_scale=(base.a2dRight-0.05, 1, 0.1), frameSize=VBase4(base.a2dLeft+0.05, base.a2dRight-0.05, -0.05, 0.05), @@ -317,6 +372,8 @@ class App(ShowBase): ) lbl.reparentTo(item) lbl.setTransparency(True) + self.actionLabels[action] = lbl + buttonScale = 0.15 btn = DirectButton( text="Change", @@ -333,10 +390,10 @@ class App(ShowBase): pos=(base.a2dRight-(0.898*buttonScale+0.3), 0, 0), pressEffect=False, command=self.changeMapping, - extraArgs=[action, lbl]) + extraArgs=[action]) btn.setTransparency(True) btn.reparentTo(item) return item -app = App() +app = MappingGUIDemo() app.run() diff --git a/samples/gamepad/models/xbone-icons.egg b/samples/gamepad/models/xbone-icons.egg new file mode 100644 index 0000000000..cc2fb417ad --- /dev/null +++ b/samples/gamepad/models/xbone-icons.egg @@ -0,0 +1,384 @@ + { Y-Up } + + xbone-icons { + xbone-icons.png + format { rgba } + alpha { dual } +} + + vpool { + 0 { + -0.5 0.5 0 + { 0.00390625 0.9921875 } + } + 1 { + -0.5 -0.5 0 + { 0.00390625 0.7578125 } + } + 2 { + 0.5 -0.5 0 + { 0.12109375 0.7578125 } + } + 3 { + 0.5 0.5 0 + { 0.12109375 0.9921875 } + } + 4 { + -0.5 0.5 0 + { 0.12890625 0.9921875 } + } + 5 { + -0.5 -0.5 0 + { 0.12890625 0.7578125 } + } + 6 { + 0.5 -0.5 0 + { 0.24609375 0.7578125 } + } + 7 { + 0.5 0.5 0 + { 0.24609375 0.9921875 } + } + 8 { + -0.5 0.5 0 + { 0.25390625 0.9921875 } + } + 9 { + -0.5 -0.5 0 + { 0.25390625 0.7578125 } + } + 10 { + 0.5 -0.5 0 + { 0.37109375 0.7578125 } + } + 11 { + 0.5 0.5 0 + { 0.37109375 0.9921875 } + } + 12 { + -0.5 0.5 0 + { 0.37890625 0.9921875 } + } + 13 { + -0.5 -0.5 0 + { 0.37890625 0.7578125 } + } + 14 { + 0.5 -0.5 0 + { 0.49609375 0.7578125 } + } + 15 { + 0.5 0.5 0 + { 0.49609375 0.9921875 } + } + 16 { + -0.5 0.5 0 + { 0.50390625 0.9921875 } + } + 17 { + -0.5 -0.5 0 + { 0.50390625 0.7578125 } + } + 18 { + 0.5 -0.5 0 + { 0.62109375 0.7578125 } + } + 19 { + 0.5 0.5 0 + { 0.62109375 0.9921875 } + } + 20 { + -0.5 0.5 0 + { 0.62890625 0.9921875 } + } + 21 { + -0.5 -0.5 0 + { 0.62890625 0.7578125 } + } + 22 { + 0.5 -0.5 0 + { 0.74609375 0.7578125 } + } + 23 { + 0.5 0.5 0 + { 0.74609375 0.9921875 } + } + 24 { + -0.5 0.5 0 + { 0.75390625 0.9921875 } + } + 25 { + -0.5 -0.5 0 + { 0.75390625 0.7578125 } + } + 26 { + 0.5 -0.5 0 + { 0.87109375 0.7578125 } + } + 27 { + 0.5 0.5 0 + { 0.87109375 0.9921875 } + } + 28 { + -0.5 0.5 0 + { 0.87890625 0.9921875 } + } + 29 { + -0.5 -0.5 0 + { 0.87890625 0.7578125 } + } + 30 { + 0.5 -0.5 0 + { 0.99609375 0.7578125 } + } + 31 { + 0.5 0.5 0 + { 0.99609375 0.9921875 } + } + 32 { + -0.5 0.5 0 + { 0.12890625 0.7421875 } + } + 33 { + -0.5 -0.5 0 + { 0.12890625 0.5078125 } + } + 34 { + 0.5 -0.5 0 + { 0.24609375 0.5078125 } + } + 35 { + 0.5 0.5 0 + { 0.24609375 0.7421875 } + } + 36 { + -0.5 0.5 0 + { 0.00390625 0.7421875 } + } + 37 { + -0.5 -0.5 0 + { 0.00390625 0.5078125 } + } + 38 { + 0.5 -0.5 0 + { 0.12109375 0.5078125 } + } + 39 { + 0.5 0.5 0 + { 0.12109375 0.7421875 } + } + 40 { + -0.5 0.5 0 + { 0.25390625 0.7421875 } + } + 41 { + -0.5 -0.5 0 + { 0.25390625 0.5078125 } + } + 42 { + 0.5 -0.5 0 + { 0.37109375 0.5078125 } + } + 43 { + 0.5 0.5 0 + { 0.37109375 0.7421875 } + } + 44 { + -0.5 0.5 0 + { 0.37890625 0.7421875 } + } + 45 { + -0.5 -0.5 0 + { 0.37890625 0.5078125 } + } + 46 { + 0.5 -0.5 0 + { 0.49609375 0.5078125 } + } + 47 { + 0.5 0.5 0 + { 0.49609375 0.7421875 } + } + 48 { + -0.5 0.5 0 + { 0.62890625 0.7421875 } + } + 49 { + -0.5 -0.5 0 + { 0.62890625 0.5078125 } + } + 50 { + 0.5 -0.5 0 + { 0.74609375 0.5078125 } + } + 51 { + 0.5 0.5 0 + { 0.74609375 0.7421875 } + } + 52 { + -0.5 0.5 0 + { 0.50390625 0.7421875 } + } + 53 { + -0.5 -0.5 0 + { 0.50390625 0.5078125 } + } + 54 { + 0.5 -0.5 0 + { 0.62109375 0.5078125 } + } + 55 { + 0.5 0.5 0 + { 0.62109375 0.7421875 } + } + 56 { + -0.5 0.5 0 + { 0.75390625 0.7421875 } + } + 57 { + -0.5 -0.5 0 + { 0.75390625 0.5078125 } + } + 58 { + 0.5 -0.5 0 + { 0.87109375 0.5078125 } + } + 59 { + 0.5 0.5 0 + { 0.87109375 0.7421875 } + } + 60 { + -0.5 0.5 0 + { 0.87890625 0.7421875 } + } + 61 { + -0.5 -0.5 0 + { 0.87890625 0.5078125 } + } + 62 { + 0.5 -0.5 0 + { 0.99609375 0.5078125 } + } + 63 { + 0.5 0.5 0 + { 0.99609375 0.7421875 } + } + 64 { + -0.5 0.5 0 + { 0.00390625 0.4921875 } + } + 65 { + -0.5 -0.5 0 + { 0.00390625 0.2578125 } + } + 66 { + 0.5 -0.5 0 + { 0.12109375 0.2578125 } + } + 67 { + 0.5 0.5 0 + { 0.12109375 0.4921875 } + } +} + face_a { + { + { xbone-icons } + { 0 1 2 3 { vpool } } + } +} + face_b { + { + { xbone-icons } + { 4 5 6 7 { vpool } } + } +} + dpad { + { + { xbone-icons } + { 8 9 10 11 { vpool } } + } +} + dpad_down { + { + { xbone-icons } + { 12 13 14 15 { vpool } } + } +} + dpad_left { + { + { xbone-icons } + { 16 17 18 19 { vpool } } + } +} + dpad_right { + { + { xbone-icons } + { 20 21 22 23 { vpool } } + } +} + dpad_up { + { + { xbone-icons } + { 24 25 26 27 { vpool } } + } +} + XboxOne_LB { + { + { xbone-icons } + { 28 29 30 31 { vpool } } + } +} + lstick { + { + { xbone-icons } + { 32 33 34 35 { vpool } } + } +} + ltrigger { + { + { xbone-icons } + { 36 37 38 39 { vpool } } + } +} + start { + { + { xbone-icons } + { 40 41 42 43 { vpool } } + } +} + rshoulder { + { + { xbone-icons } + { 44 45 46 47 { vpool } } + } +} + rstick { + { + { xbone-icons } + { 48 49 50 51 { vpool } } + } +} + rtrigger { + { + { xbone-icons } + { 52 53 54 55 { vpool } } + } +} + back { + { + { xbone-icons } + { 56 57 58 59 { vpool } } + } +} + face_x { + { + { xbone-icons } + { 60 61 62 63 { vpool } } + } +} + face_y { + { + { xbone-icons } + { 64 65 66 67 { vpool } } + } +} diff --git a/samples/gamepad/models/xbone-icons.png b/samples/gamepad/models/xbone-icons.png new file mode 100644 index 0000000000000000000000000000000000000000..e8fd66b487f8895846b3c2270cb6eca4be34fcca GIT binary patch literal 29275 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 Date: Sat, 29 Sep 2018 15:17:58 +0200 Subject: [PATCH 277/424] Updated steering wheel sample made steering wheel sample work with current version of input classes --- samples/gamepad/steeringWheel.py | 105 +++++++++++++++++++------------ 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py index 306419c378..65e5aa63f4 100644 --- a/samples/gamepad/steeringWheel.py +++ b/samples/gamepad/steeringWheel.py @@ -11,19 +11,21 @@ from direct.showbase.ShowBase import ShowBase from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 from direct.gui.OnscreenText import OnscreenText -loadPrcFileData("", "notify-level-device debug") +loadPrcFileData("", """ + default-fov 60 + notify-level-device debug +""") class App(ShowBase): def __init__(self): ShowBase.__init__(self) - # print all events sent through the messenger - self.messenger.toggleVerbose() + # Print all events sent through the messenger + #self.messenger.toggleVerbose() self.lblWarning = OnscreenText( text = "No devices found", fg=(1,0,0,1), scale = .25) - self.lblWarning.hide() self.lblAction = OnscreenText( text = "Action", @@ -31,7 +33,11 @@ class App(ShowBase): scale = .15) self.lblAction.hide() - self.checkDevices() + # Is there a steering wheel connected? + self.wheel = None + devices = self.devices.getDevices(InputDevice.DC_steering_wheel) + if devices: + self.connect(devices[0]) self.currentMoveSpeed = 0.0 self.maxAccleration = 28.0 @@ -40,17 +46,15 @@ class App(ShowBase): self.maxSpeed = 80.0 # Accept device dis-/connection events - # NOTE: catching the events here will overwrite the accept in showbase, hence - # we need to forward the event in the functions we set here! self.accept("connect-device", self.connect) self.accept("disconnect-device", self.disconnect) self.accept("escape", exit) - self.accept("flight_stick0-start", exit) # Accept button events of the first connected steering wheel - self.accept("steering_wheel0-action_a", self.doAction, extraArgs=[True, "Action"]) - self.accept("steering_wheel0-action_a-up", self.doAction, extraArgs=[False, "Release"]) + self.accept("steering_wheel0-face_a", self.action, extraArgs=["Action"]) + self.accept("steering_wheel0-face_a-up", self.actionUp) + self.accept("steering_wheel0-hat_up", self.center_wheel) self.environment = loader.loadModel("environment") self.environment.reparentTo(render) @@ -60,51 +64,74 @@ class App(ShowBase): # In real world applications, you should notice the user and give him enough time # to center the wheel until you store the center position of the controler! self.wheelCenter = 0 - wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) - if len(wheels) > 0: - self.wheelCenter = wheels[0].findControl(InputDevice.C_wheel).state + if self.wheel is not None: + self.wheelCenter = self.wheel.findAxis(InputDevice.Axis.wheel).value # disable pandas default mouse-camera controls so we can handle the camera # movements by ourself self.disableMouse() - base.camera.setZ(2) + self.reset() self.taskMgr.add(self.moveTask, "movement update task") def connect(self, device): - # we need to forward the event to the connectDevice function of showbase - self.connectDevice(device) - # Now we can check for ourself - self.checkDevices() + """Event handler that is called when a device is discovered.""" + + # We're only interested if this is a steering wheel and we don't have a + # wheel yet. + if device.device_class == InputDevice.DC_steering_wheel and not self.wheel: + print("Found %s" % (device)) + self.wheel = device + + # Enable this device to ShowBase so that we can receive events. + # We set up the events with a prefix of "steering_wheel0-". + self.attachInputDevice(device, prefix="steering_wheel0") + + # Hide the warning that we have no devices. + self.lblWarning.hide() def disconnect(self, device): - # we need to forward the event to the disconnectDevice function of showbase - self.disconnectDevice(device) - # Now we can check for ourself - self.checkDevices() + """Event handler that is called when a device is removed.""" - def checkDevices(self): - # check if we have wheel devices connected - if self.devices.get_devices(InputDevice.DC_steering_wheel): - # we have at least one steering wheel device - self.lblWarning.hide() + if self.wheel != device: + # We don't care since it's not our wheel. + return + + # Tell ShowBase that the device is no longer needed. + print("Disconnected %s" % (device)) + self.detachInputDevice(device) + self.wheel = None + + # Do we have any steering wheels? Attach the first other steering wheel. + devices = self.devices.getDevices(InputDevice.DC_steering_wheel) + if devices: + self.connect(devices[0]) else: - # no devices connected + # No devices. Show the warning. self.lblWarning.show() - def doAction(self, showText, text): - if showText and self.lblAction.isHidden(): - self.lblAction.show() - else: - self.lblAction.hide() + def reset(self): + """Reset the camera to the initial position.""" + self.camera.setPosHpr(0, -200, 2, 0, 0, 0) + + def action(self, button): + # Just show which button has been pressed. + self.lblAction.text = "Pressed %s" % button + self.lblAction.show() + + def actionUp(self): + # Hide the label showing which button is pressed. + self.lblAction.hide() + + def center_wheel(self): + """Reset the wheels center rotation to the current rotation of the wheel""" + self.wheelCenter = self.wheel.findAxis(InputDevice.Axis.wheel).value def moveTask(self, task): dt = globalClock.getDt() movementVec = Vec3() - wheels = base.devices.getDevices(InputDevice.DC_steering_wheel) - if len(wheels) == 0: - # savety check + if not self.wheel: return task.cont if self.currentMoveSpeed > 0: @@ -114,21 +141,21 @@ class App(ShowBase): # we will use the first found wheel # Acclerate - acclearatorPedal = wheels[0].findControl(InputDevice.C_accelerator).state + accleratorPedal = self.wheel.findAxis(InputDevice.Axis.accelerator).value accleration = accleratorPedal * self.maxAccleration if self.currentMoveSpeed > accleratorPedal * self.maxSpeed: self.currentMoveSpeed -= dt * self.deaccleration self.currentMoveSpeed += dt * accleration # Break - breakPedal = wheels[0].findControl(InputDevice.C_brake).state + breakPedal = self.wheel.findAxis(InputDevice.Axis.brake).value deacleration = breakPedal * self.deaclerationBreak self.currentMoveSpeed -= dt * deacleration if self.currentMoveSpeed < 0: self.currentMoveSpeed = 0 # Steering - rotation = self.wheelCenter - wheels[0].findControl(InputDevice.C_wheel).state + rotation = self.wheelCenter - self.wheel.findAxis(InputDevice.Axis.wheel).value base.camera.setH(base.camera, 100 * dt * rotation) # calculate movement From d764574d071bd40c9b35f3083582303354cd889f Mon Sep 17 00:00:00 2001 From: fireclawthefox Date: Sat, 29 Sep 2018 15:23:54 +0200 Subject: [PATCH 278/424] Added updated flight stick sample --- samples/gamepad/flightstick.py | 168 +++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 samples/gamepad/flightstick.py diff --git a/samples/gamepad/flightstick.py b/samples/gamepad/flightstick.py new file mode 100644 index 0000000000..1c4504330c --- /dev/null +++ b/samples/gamepad/flightstick.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +''' +Demonstrate usage of steering wheels + +In this sample you can use a wheel type device to control the camera and +show some messages on screen. You can acclerate forward using the +accleration pedal and slow down using the break pedal. +''' + +from direct.showbase.ShowBase import ShowBase +from panda3d.core import TextNode, InputDevice, loadPrcFileData, Vec3 +from direct.gui.OnscreenText import OnscreenText + +loadPrcFileData("", """ + default-fov 60 + notify-level-device debug +""") + +class App(ShowBase): + def __init__(self): + ShowBase.__init__(self) + # Print all events sent through the messenger + #self.messenger.toggleVerbose() + + self.lblWarning = OnscreenText( + text = "No devices found", + fg=(1,0,0,1), + scale = .25) + + self.lblAction = OnscreenText( + text = "Action", + fg=(1,1,1,1), + scale = .15) + self.lblAction.hide() + + # Is there a gamepad connected? + self.flightStick = None + devices = self.devices.getDevices(InputDevice.DC_flight_stick) + if devices: + self.connect(devices[0]) + + self.currentMoveSpeed = 0.0 + self.maxAccleration = 28.0 + self.deaccleration = 10.0 + self.deaclerationBreak = 37.0 + self.maxSpeed = 80.0 + + # Accept device dis-/connection events + self.accept("connect-device", self.connect) + self.accept("disconnect-device", self.disconnect) + + self.accept("escape", exit) + self.accept("flight_stick0-start", exit) + + # Accept button events of the first connected flight stick + self.accept("flight_stick0-action_a", self.action, extraArgs=["Action"]) + self.accept("flight_stick0-action_a-up", self.actionUp) + + self.environment = loader.loadModel("environment") + self.environment.reparentTo(render) + + # save the center position of the stick + # NOTE: here we assume, that the stick is centered when the application get started. + # In real world applications, you should notice the user and give him enough time + # to center the wheel until you store the center position of the controler! + self.hcenter = 0 + self.xcenter = 0 + self.ycenter = 0 + if self.flightStick: + self.hcenter = self.flightStick.findAxis(InputDevice.Axis.pitch).value + self.xcenter = self.flightStick.findAxis(InputDevice.Axis.x).value + self.ycenter = self.flightStick.findAxis(InputDevice.Axis.y).value + + # disable pandas default mouse-camera controls so we can handle the camera + # movements by ourself + self.disableMouse() + self.reset() + + self.taskMgr.add(self.moveTask, "movement update task") + + def connect(self, device): + """Event handler that is called when a device is discovered.""" + + # We're only interested if this is a flight stick and we don't have a + # flight stick yet. + if device.device_class == InputDevice.DC_flight_stick and not self.flightStick: + print("Found %s" % (device)) + self.flightStick = device + + # Enable this device to ShowBase so that we can receive events. + # We set up the events with a prefix of "flight_stick0-". + self.attachInputDevice(device, prefix="flight_stick0") + + # Hide the warning that we have no devices. + self.lblWarning.hide() + + def disconnect(self, device): + """Event handler that is called when a device is removed.""" + + if self.flightStick != device: + # We don't care since it's not our gamepad. + return + + # Tell ShowBase that the device is no longer needed. + print("Disconnected %s" % (device)) + self.detachInputDevice(device) + self.flightStick = None + + # Do we have any other gamepads? Attach the first other gamepad. + devices = self.devices.getDevices(InputDevice.DC_flight_stick) + if devices: + self.connect(devices[0]) + else: + # No devices. Show the warning. + self.lblWarning.show() + + def reset(self): + """Reset the camera to the initial position.""" + + self.camera.setPosHpr(0, -200, 10, 0, 0, 0) + + def action(self, button): + # Just show which button has been pressed. + self.lblAction.text = "Pressed %s" % button + self.lblAction.show() + + def actionUp(self): + # Hide the label showing which button is pressed. + self.lblAction.hide() + + def moveTask(self, task): + dt = globalClock.getDt() + + if not self.flightStick: + return task.cont + + if self.currentMoveSpeed > 0: + self.currentMoveSpeed -= dt * self.deaccleration + if self.currentMoveSpeed < 0: + self.currentMoveSpeed = 0 + + # we will use the first found wheel + # Acclerate + throttle = self.flightStick.findAxis(InputDevice.Axis.throttle).value + accleration = throttle * self.maxAccleration + if self.currentMoveSpeed > throttle * self.maxSpeed: + self.currentMoveSpeed -= dt * self.deaccleration + self.currentMoveSpeed += dt * accleration + + # Steering + + # Control the cameras yaw/Headding + stick_yaw = self.flightStick.findAxis(InputDevice.Axis.yaw) + base.camera.setH(base.camera, 100 * dt * (stick_yaw.value - self.hcenter)) + # Control the cameras pitch + stick_y = self.flightStick.findAxis(InputDevice.Axis.pitch) + base.camera.setP(base.camera, 100 * dt * (stick_y.value - self.ycenter)) + # Control the cameras roll + stick_X = self.flightStick.findAxis(InputDevice.Axis.roll) + base.camera.setR(base.camera, 100 * dt * (stick_X.value - self.xcenter)) + + # calculate movement + base.camera.setY(base.camera, dt * self.currentMoveSpeed) + + return task.cont + +app = App() +app.run() From 33e6b6bf7a2352e5b770c748eb6c0cfadb6b93fb Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 4 Oct 2018 22:07:34 -0700 Subject: [PATCH 279/424] deploy-ng: Setup system dependencies to ignore on Linux and macOS --- direct/src/showutil/dist.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 17cb34c1fc..bd5b9a3f42 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -219,11 +219,18 @@ class build_apps(setuptools.Command): 'oleaut32.dll', 'gdiplus.dll', 'winmm.dll', 'iphlpapi.dll', 'msvcrt.dll', 'kernelbase.dll', 'msimg32.dll', 'msacm32.dll', - # manylinux1 - 'libdl.so.2', 'libstdc++.so.6', 'libm.so.6', 'libgcc_s.so.1', - 'libpthread.so.0', 'libc.so.6', 'ld-linux-x86-64.so.2', - 'libgl.so.1', 'libx11.so.6', 'libreadline.so.5', 'libncursesw.so.5', - 'libbz2.so.1', 'libz.so.1', 'liblzma.so.0', 'librt.so.1', 'libutil.so.1', + # manylinux1/linux + 'libdl.so.*', 'libstdc++.so.*', 'libm.so.*', 'libgcc_s.so.*', + 'libpthread.so.*', 'libc.so.*', 'ld-linux-x86-64.so.*', + 'libgl.so.*', 'libx11.so.*', 'libreadline.so.*', 'libncursesw.so.*', + 'libbz2.so.*', 'libz.so.*', 'liblzma.so.*', 'librt.so.*', 'libutil.so.*', + + # macOS + '*foundation.framework*', '*appkit.framework*', '*libstdc++.*.dylib', + '*libobjc.*.dylib', '*trustevaluationagent.framework*', '*libz.*.dylib', + '*coreservices.framework*', '*applicationservices.framework*', + '*opengl.framework*', '*carbon.framework*', '*cocoa.framework*', + '*libsystem.*.dylib' ] self.package_data_dirs = {} @@ -284,6 +291,8 @@ class build_apps(setuptools.Command): assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps' + self.exclude_dependencies = [p3d.GlobPattern(i) for i in self.exclude_dependencies] + tmp = self.default_file_handlers.copy() tmp.update(self.file_handlers) self.file_handlers = tmp @@ -803,8 +812,9 @@ class build_apps(setuptools.Command): # We've already added it earlier. return - if name.lower() in self.exclude_dependencies: - return + for dep in self.exclude_dependencies: + if dep.matches(name.lower()): + return for dir in search_path: source_path = os.path.join(dir, name) @@ -851,7 +861,7 @@ class build_apps(setuptools.Command): # Warn if we can't find it, but only once. self.warn("could not find dependency {0} (referenced by {1})".format(name, referenced_by)) - self.exclude_dependencies.append(name.lower()) + self.exclude_dependencies.append(p3d.GlobPattern(name.lower())) def copy(self, source_path, target_path): """ Copies source_path to target_path. From 3d66e2290557fffb6c2807da78ecccc2f69f829d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:47:59 +0200 Subject: [PATCH 280/424] samples: improvements to flight stick sample program --- samples/gamepad/flightstick.py | 49 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/samples/gamepad/flightstick.py b/samples/gamepad/flightstick.py index 1c4504330c..0a06c52585 100644 --- a/samples/gamepad/flightstick.py +++ b/samples/gamepad/flightstick.py @@ -1,10 +1,9 @@ #!/usr/bin/env python ''' -Demonstrate usage of steering wheels +Demonstrate usage of flight stick -In this sample you can use a wheel type device to control the camera and -show some messages on screen. You can acclerate forward using the -accleration pedal and slow down using the break pedal. +In this sample you can use a flight stick to control the camera and show some +messages on screen. You can accelerate using the throttle. ''' from direct.showbase.ShowBase import ShowBase @@ -16,6 +15,10 @@ loadPrcFileData("", """ notify-level-device debug """) +STICK_DEAD_ZONE = 0.02 +THROTTLE_DEAD_ZONE = 0.02 + + class App(ShowBase): def __init__(self): ShowBase.__init__(self) @@ -53,24 +56,12 @@ class App(ShowBase): self.accept("flight_stick0-start", exit) # Accept button events of the first connected flight stick - self.accept("flight_stick0-action_a", self.action, extraArgs=["Action"]) - self.accept("flight_stick0-action_a-up", self.actionUp) + self.accept("flight_stick0-trigger", self.action, extraArgs=["Trigger"]) + self.accept("flight_stick0-trigger-up", self.actionUp) self.environment = loader.loadModel("environment") self.environment.reparentTo(render) - # save the center position of the stick - # NOTE: here we assume, that the stick is centered when the application get started. - # In real world applications, you should notice the user and give him enough time - # to center the wheel until you store the center position of the controler! - self.hcenter = 0 - self.xcenter = 0 - self.ycenter = 0 - if self.flightStick: - self.hcenter = self.flightStick.findAxis(InputDevice.Axis.pitch).value - self.xcenter = self.flightStick.findAxis(InputDevice.Axis.x).value - self.ycenter = self.flightStick.findAxis(InputDevice.Axis.y).value - # disable pandas default mouse-camera controls so we can handle the camera # movements by ourself self.disableMouse() @@ -139,9 +130,10 @@ class App(ShowBase): if self.currentMoveSpeed < 0: self.currentMoveSpeed = 0 - # we will use the first found wheel - # Acclerate + # Accelerate using the throttle. Apply deadzone of 0.01. throttle = self.flightStick.findAxis(InputDevice.Axis.throttle).value + if abs(throttle) < THROTTLE_DEAD_ZONE: + throttle = 0 accleration = throttle * self.maxAccleration if self.currentMoveSpeed > throttle * self.maxSpeed: self.currentMoveSpeed -= dt * self.deaccleration @@ -151,17 +143,26 @@ class App(ShowBase): # Control the cameras yaw/Headding stick_yaw = self.flightStick.findAxis(InputDevice.Axis.yaw) - base.camera.setH(base.camera, 100 * dt * (stick_yaw.value - self.hcenter)) + if abs(stick_yaw.value) > STICK_DEAD_ZONE: + base.camera.setH(base.camera, 100 * dt * stick_yaw.value) + # Control the cameras pitch stick_y = self.flightStick.findAxis(InputDevice.Axis.pitch) - base.camera.setP(base.camera, 100 * dt * (stick_y.value - self.ycenter)) + if abs(stick_y.value) > STICK_DEAD_ZONE: + base.camera.setP(base.camera, 100 * dt * stick_y.value) + # Control the cameras roll - stick_X = self.flightStick.findAxis(InputDevice.Axis.roll) - base.camera.setR(base.camera, 100 * dt * (stick_X.value - self.xcenter)) + stick_x = self.flightStick.findAxis(InputDevice.Axis.roll) + if abs(stick_x.value) > STICK_DEAD_ZONE: + base.camera.setR(base.camera, 100 * dt * stick_x.value) # calculate movement base.camera.setY(base.camera, dt * self.currentMoveSpeed) + # Make sure camera does not go below the ground. + if base.camera.getZ() < 1: + base.camera.setZ(1) + return task.cont app = App() From f518b9aaf1acfea4a311b66e0335fac3f44d4c12 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:48:39 +0200 Subject: [PATCH 281/424] device: fixes to sporadic device class detection on Linux --- panda/src/device/evdevInputDevice.cxx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 4fdd451cd5..3a9a394eec 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -239,8 +239,7 @@ init_device() { LightMutexHolder holder(_lock); - uint8_t evtypes[(EV_MAX + 8) >> 3]; - memset(evtypes, 0, sizeof(evtypes)); + uint8_t evtypes[(EV_MAX + 8) >> 3] = {0}; char name[128]; if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) < 0 || ioctl(_fd, EVIOCGBIT(0, sizeof(evtypes)), evtypes) < 0) { @@ -267,10 +266,9 @@ init_device() { bool has_keys = false; bool has_axes = false; - uint8_t keys[(KEY_MAX + 8) >> 3]; + uint8_t keys[(KEY_MAX + 8) >> 3] = {0}; if (test_bit(EV_KEY, evtypes)) { // Check which buttons are on the device. - memset(keys, 0, sizeof(keys)); ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys); has_keys = true; @@ -280,10 +278,9 @@ init_device() { } int num_bits = 0; - uint8_t axes[(ABS_MAX + 8) >> 3]; + uint8_t axes[(ABS_MAX + 8) >> 3] = {0}; if (test_bit(EV_ABS, evtypes)) { // Check which axes are on the device. - memset(axes, 0, sizeof(axes)); num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3; has_axes = true; } @@ -302,8 +299,7 @@ init_device() { // Try to detect which type of device we have here if (_device_class == DC_unknown) { - int device_scores[DC_COUNT]; - memset(device_scores, 0, sizeof(device_scores)); + int device_scores[DC_COUNT] = {0}; // Test for specific keys if (test_bit(BTN_GAMEPAD, keys) && test_bit(ABS_X, axes) && test_bit(ABS_RX, axes)) { @@ -374,8 +370,7 @@ init_device() { if (has_keys) { // Also check whether the buttons are currently pressed. - uint8_t states[(KEY_MAX + 8) >> 3]; - memset(states, 0, sizeof(states)); + uint8_t states[(KEY_MAX + 8) >> 3] = {0}; ioctl(_fd, EVIOCGKEY(sizeof(states)), states); for (int i = 0; i <= KEY_MAX; ++i) { @@ -565,8 +560,7 @@ init_device() { } if (test_bit(EV_FF, evtypes)) { - uint8_t effects[(FF_MAX + 8) >> 3]; - memset(effects, 0, sizeof(effects)); + uint8_t effects[(FF_MAX + 8) >> 3] = {0}; ioctl(_fd, EVIOCGBIT(EV_FF, sizeof(effects)), effects); if (test_bit(FF_RUMBLE, effects)) { From 4b77812b3528c1b12b795c8fb84b60cb29ab783c Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 20:49:13 +0200 Subject: [PATCH 282/424] device: fix inverted flight stick throttle on Linux --- panda/src/device/evdevInputDevice.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 3a9a394eec..4cdf52a880 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -540,7 +540,7 @@ init_device() { std::swap(absinfo.maximum, absinfo.minimum); } if (axis == Axis::throttle && (quirks & QB_centered_throttle) != 0) { - index = add_axis(axis, absinfo.maximum, absinfo.minimum, true); + index = add_axis(axis, absinfo.minimum, absinfo.maximum, true); } else { index = add_axis(axis, absinfo.minimum, absinfo.maximum); } From 9618cd219368e0335d233ae8b8a64c7cc42ce18f Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 7 Oct 2018 21:06:24 +0200 Subject: [PATCH 283/424] device: ensure libputil is initialized before scanning devices --- panda/src/device/inputDeviceManager.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 140b4379f1..4bc69963eb 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -33,6 +33,8 @@ InputDeviceManager() : _lock("InputDeviceManager") { */ void InputDeviceManager:: make_global_ptr() { + init_libputil(); + #ifdef _WIN32 _global_ptr = new WinInputDeviceManager; #elif defined(__APPLE__) From 90b0f501dc49e2d6f27834b19bd43ea7f5e241bb Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sun, 7 Oct 2018 12:31:17 -0700 Subject: [PATCH 284/424] deply-ng: Add libedit.*.dylib to list of macOS system libraries --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index bd5b9a3f42..2bf6602c97 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -230,7 +230,7 @@ class build_apps(setuptools.Command): '*libobjc.*.dylib', '*trustevaluationagent.framework*', '*libz.*.dylib', '*coreservices.framework*', '*applicationservices.framework*', '*opengl.framework*', '*carbon.framework*', '*cocoa.framework*', - '*libsystem.*.dylib' + '*libsystem.*.dylib', '*libedit.*.dylib' ] self.package_data_dirs = {} From ada3fa5513ee5496fb1ff280c73d5c7cfd911269 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 8 Oct 2018 01:44:17 +0200 Subject: [PATCH 285/424] Compile fixes on input-overhaul branch --- panda/src/device/evdevInputDevice.cxx | 1 - panda/src/device/inputDeviceManager.cxx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 4cdf52a880..6fda488b0f 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -673,7 +673,6 @@ process_events() { int y = _pointer_data.get_y(); bool have_pointer = false; double time = ClockObject::get_global_clock()->get_frame_time(); - ButtonHandle button; int index; // It seems that some devices send a single EV_SYN event when being diff --git a/panda/src/device/inputDeviceManager.cxx b/panda/src/device/inputDeviceManager.cxx index 4bc69963eb..8cd470e271 100644 --- a/panda/src/device/inputDeviceManager.cxx +++ b/panda/src/device/inputDeviceManager.cxx @@ -16,6 +16,7 @@ #include "linuxInputDeviceManager.h" #include "winInputDeviceManager.h" #include "throw_event.h" +#include "config_putil.h" InputDeviceManager *InputDeviceManager::_global_ptr = nullptr; From 665bdb16eafdd918905d5de6219ccdec0b90234d Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 8 Oct 2018 18:36:23 -0700 Subject: [PATCH 286/424] deploy-ng: Add options for using optimized wheels for panda3d This works by adding extra index urls. Optimized wheels have local version tags that should take precedence over non-optimized wheels. If no optimized wheel if found for a platform, a warning is issued and a non-optimized wheel is used instead. --- direct/src/showutil/dist.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2bf6602c97..608371de14 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -7,6 +7,7 @@ import plistlib import sys import subprocess import zipfile +import re import shutil import stat import struct @@ -207,6 +208,8 @@ class build_apps(setuptools.Command): self.log_filename = None self.log_append = False self.requirements_path = './requirements.txt' + self.use_optimized_wheels = True + self.optimized_wheel_index = '' self.pypi_extra_indexes = [] self.file_handlers = {} self.exclude_dependencies = [ @@ -288,6 +291,17 @@ class build_apps(setuptools.Command): subprocess.check_call(['pipenv', 'lock', '--requirements'], stdout=reqsfile) self.requirements_path = reqspath + if self.use_optimized_wheels: + if not self.optimized_wheel_index: + # Try to find an appropriate wheel index + with open(self.requirements_path) as reqsfile: + reqsdata = reqsfile.read() + matches = re.search(r'--extra-index-url (https*://archive.panda3d.org/.*\b)', reqsdata) + if matches and matches.group(1): + self.optimized_wheel_index = matches.group(1) + '/opt' + + assert self.optimized_wheel_index, 'An index for optimized wheels must be defined if use_optimized_wheels is set' + assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path) assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps' @@ -362,6 +376,11 @@ class build_apps(setuptools.Command): '--abi', abi_tag ] + if self.use_optimized_wheels: + pip_args += [ + '--extra-index-url', self.optimized_wheel_index + ] + for index in self.pypi_extra_indexes: pip_args += ['--extra-index-url', index] @@ -452,6 +471,15 @@ class build_apps(setuptools.Command): else: raise RuntimeError("Missing panda3d wheel for platform: {}".format(platform)) + if self.use_optimized_wheels: + # Check to see if we have an optimized wheel + localtag = p3dwhlfn.split('+')[1].split('-')[0] + if not localtag.endswith('opt'): + self.announce( + 'Could not find an optimized wheel (using index {}) for platform: {}'.format(self.optimized_wheel_index, platform), + distutils.log.WARN + ) + #whlfiles = {whl: self._get_zip_file(whl) for whl in wheelpaths} # Add whl files to the path so they are picked up by modulefinder From ed489ac03a03000a5815e7911338ece1f08f0708 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 16 Oct 2018 23:03:44 +0200 Subject: [PATCH 287/424] deploy-ng: fix egg2bam error handling on Python 2.7 --- direct/src/showutil/dist.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 608371de14..78c95fc8d5 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -26,6 +26,11 @@ if 'basestring' not in globals(): basestring = str +if sys.version_info < (3, 0): + # Python 3 defines these subtypes of IOError, but Python 2 doesn't. + FileNotFoundError = IOError + + def _parse_list(input): if isinstance(input, basestring): input = input.strip().replace(',', '\n') @@ -59,7 +64,7 @@ def egg2bam(_build_cmd, srcpath, dstpath): ]) except FileNotFoundError: raise RuntimeError('egg2bam failed: egg2bam was not found in the PATH') - except subprocess.SubprocessError as err: + except subprocess.CalledProcessError as err: raise RuntimeError('egg2bam failed: {}'.format(err)) return dstpath From 6f311007f5116625507a668c357e884f78852446 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 17 Oct 2018 22:08:00 -0700 Subject: [PATCH 288/424] deploy-ng: Do not error if the panda3d wheel has no localtag --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 78c95fc8d5..91ba81cb22 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -478,7 +478,7 @@ class build_apps(setuptools.Command): if self.use_optimized_wheels: # Check to see if we have an optimized wheel - localtag = p3dwhlfn.split('+')[1].split('-')[0] + localtag = p3dwhlfn.split('+')[1].split('-')[0] if '+' in p3dwhlfn else '' if not localtag.endswith('opt'): self.announce( 'Could not find an optimized wheel (using index {}) for platform: {}'.format(self.optimized_wheel_index, platform), From 18c1e242f8326e4a769ebe41418fbfd175ed416d Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 17 Oct 2018 22:15:54 -0700 Subject: [PATCH 289/424] deploy-ng: Include all encodings by default For Python 3.7 on 64bit Linux, this adds about 300-400KB to built applications. --- direct/src/showutil/FreezeTool.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 83b1ea89f0..d745a5281a 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -35,16 +35,14 @@ isDebugBuild = (python.lower().endswith('_d')) # NB. if encodings are removed, be sure to remove them from the shortcut in # deploy-stub.c. startupModules = [ - 'encodings', 'encodings.aliases', 'encodings.undefined', 'encodings.ascii', - 'encodings.cp1252', 'encodings.latin_1', 'encodings.utf_8', - 'encodings.mbcs', 'encodings.cp850', 'encodings.cp437', 'imp', - ] + 'imp', 'encodings', 'encodings.*', +] if sys.version_info >= (3, 0): # Modules specific to Python 3 startupModules += ['io', 'marshal', 'importlib.machinery', 'importlib.util'] else: # Modules specific to Python 2 - startupModules += ['encodings.string_escape'] + startupModules += [] # These are some special init functions for some built-in Python modules that # deviate from the standard naming convention. A value of None means that a @@ -1048,7 +1046,7 @@ class Freezer: for moduleName in startupModules: if moduleName not in self.modules: - self.modules[moduleName] = self.ModuleDef(moduleName, implicit = True) + self.addModule(moduleName, implicit = True) # Excluding a parent module also excludes all its # (non-explicit) children, unless the parent has allowChildren From 305621338f05b59f6f0d829daa6c57f4ec9a8426 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 28 Oct 2018 20:47:44 +0100 Subject: [PATCH 290/424] deploy-ng: fix wrong platform tag for 32-bit Linux in bdist_apps --- direct/src/showutil/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 91ba81cb22..0e3f87bff2 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1126,7 +1126,7 @@ class build_apps(setuptools.Command): class bdist_apps(setuptools.Command): DEFAULT_INSTALLERS = { 'manylinux1_x86_64': ['gztar'], - 'manylinux1_i386': ['gztar'], + 'manylinux1_i686': ['gztar'], # Everything else defaults to ['zip'] } From 55371e0aad12b2b7a97f11c849927a6c51662280 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 28 Oct 2018 21:18:12 +0100 Subject: [PATCH 291/424] deploy-ng: fix some issues finding relative library deps in wheels In particular, this fixes deploying with numpy for macOS, since the numpy whl has library dependencies with .. in the path. --- direct/src/showutil/dist.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 0e3f87bff2..e5f5ba4551 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -864,7 +864,7 @@ class build_apps(setuptools.Command): whlfile = self._get_zip_file(whl) # Normalize the path separator - wf = wf.replace(os.path.sep, '/') + wf = os.path.normpath(wf).replace(os.path.sep, '/') # Look case-insensitively. namelist = whlfile.namelist() @@ -935,9 +935,10 @@ class build_apps(setuptools.Command): self.copy(source_path, target_path) fp = open(target_path, 'rb') + source_dir = os.path.dirname(source_path) target_dir = os.path.dirname(target_path) base = os.path.basename(target_path) - self.copy_dependencies(fp, target_dir, search_path, base) + self.copy_dependencies(fp, target_dir, search_path + [source_dir], base) def copy_dependencies(self, fp, target_dir, search_path, referenced_by): """ Copies the dependencies of the given open file. """ From a42ebaea74df4fc91793775369a8076f1261eed6 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 3 Nov 2018 12:13:51 -0700 Subject: [PATCH 292/424] deploy-ng: Allow platforms to be specified on the command line This makes it easier to build for one platform even if multiple platforms are specified in setup.py/cfg. For example: python setup.py build_apps -p win_amd64 Or, for multiple platforms: python setup.py build_apps -p win32,win_amd64 --- direct/src/showutil/dist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index e5f5ba4551..8668fd4e73 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -189,6 +189,7 @@ class build_apps(setuptools.Command): user_options = [ ('build-base=', None, 'directory to build applications in'), ('requirements-path=', None, 'path to requirements.txt file for pip'), + ('platforms=', 'p', 'a list of platforms to build for'), ] default_file_handlers = { '.egg': egg2bam, From d9256755e3a2a8e72cd3c5a2f33b2930a35fb145 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 3 Nov 2018 12:32:29 -0700 Subject: [PATCH 293/424] deploy-ng: Allow setting build_apps command line options via bdist_apps command line options --- direct/src/showutil/dist.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 8668fd4e73..2e348ed629 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -1133,10 +1133,16 @@ class bdist_apps(setuptools.Command): } description = 'bundle built Panda3D applications into distributable forms' - user_options = [] + user_options = build_apps.user_options + [ + ] + + def _build_apps_options(self): + return [opt[0].replace('-', '_').replace('=', '') for opt in build_apps.user_options] def initialize_options(self): self.installers = {} + for opt in self._build_apps_options(): + setattr(self, opt, None) def finalize_options(self): # We need to massage the inputs a bit in case they came from a @@ -1283,14 +1289,16 @@ class bdist_apps(setuptools.Command): subprocess.check_call(cmd) def run(self): - build_cmd = self.get_finalized_command('build_apps') - if not build_cmd.platforms: - platforms = [p3d.PandaSystem.get_platform()] - else: - platforms = build_cmd.platforms - build_base = build_cmd.build_base - + build_cmd = self.distribution.get_command_obj('build_apps') + for opt in self._build_apps_options(): + optval = getattr(self, opt) + if optval is not None: + setattr(build_cmd, opt, optval) + build_cmd.finalize_options() self.run_command('build_apps') + + platforms = build_cmd.platforms + build_base = build_cmd.build_base os.chdir(build_base) for platform in platforms: From ea832efb812d4504c4312531eb77950756b1a043 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 12 Nov 2018 18:32:48 +0100 Subject: [PATCH 294/424] deploy-ng: fix run error with frozen attr module --- direct/src/showutil/FreezeTool.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index cf8c6c4bff..45ae698413 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -89,11 +89,14 @@ overrideModules = { # lines from the main executable, which we don't want. 'linecache': """__all__ = ["getline", "clearcache", "checkcache"] +cache = {} + def getline(filename, lineno, module_globals=None): return '' def clearcache(): - pass + global cache + cache = {} def getlines(filename, module_globals=None): return [] From 0c1572ce336cac034ee6931b4d5dce044e686523 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 17 Nov 2018 14:19:04 -0800 Subject: [PATCH 295/424] deploy-ng: Fix reference (e.g., texture) paths when using egg2bam Fixes #277 --- direct/src/showutil/dist.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2e348ed629..89ec394d6c 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -58,8 +58,9 @@ def egg2bam(_build_cmd, srcpath, dstpath): try: subprocess.check_call([ 'egg2bam', - '-o', - dstpath, + '-o', dstpath, + '-pd', os.path.dirname(os.path.abspath(srcpath)), + '-ps', 'rel', srcpath ]) except FileNotFoundError: From 37d2ff64ded0786d07ea22a2c15fbe460dc8e1e5 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 25 Nov 2018 23:38:07 +0100 Subject: [PATCH 296/424] putil: add id and pressure fields to PointerData --- panda/src/putil/pointerData.I | 54 ++++++++++++++--------------------- panda/src/putil/pointerData.h | 19 +++++++----- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/panda/src/putil/pointerData.I b/panda/src/putil/pointerData.I index 6697c31d1a..a23b1c464b 100644 --- a/panda/src/putil/pointerData.I +++ b/panda/src/putil/pointerData.I @@ -11,37 +11,6 @@ * @date 2002-07-15 */ -/** - * - */ -INLINE PointerData:: -PointerData() { - _in_window = false; - _xpos = 0; - _ypos = 0; -} - -/** - * - */ -INLINE PointerData:: -PointerData(const PointerData ©) : - _in_window(copy._in_window), - _xpos(copy._xpos), - _ypos(copy._ypos) -{ -} - -/** - * - */ -INLINE void PointerData:: -operator = (const PointerData ©) { - _in_window = copy._in_window; - _xpos = copy._xpos; - _ypos = copy._ypos; -} - /** * */ @@ -59,13 +28,34 @@ get_y() const { } /** - * + * If this returns false, the pointer is not currently present in the window + * and the values returned by get_x() and get_y() may not be meaningful. */ INLINE bool PointerData:: get_in_window() const { return _in_window; } +/** + * Returns a unique identifier for this pointer. This is for tracking + * individual fingers. This value should not be assumed to have a specific + * meaning other than that there will not be two different pointers active + * simultaneously with the same identifier. + */ +INLINE int PointerData:: +get_id() const { + return _id; +} + +/** + * Returns the pressure of the pointer. For mice, this will be 1.0 if any + * button is pressed, 0.0 otherwise. + */ +INLINE double PointerData:: +get_pressure() const { + return _pressure; +} + INLINE std::ostream &operator << (std::ostream &out, const PointerData &md) { md.output(out); diff --git a/panda/src/putil/pointerData.h b/panda/src/putil/pointerData.h index aa3f3aeb62..ab0aee374e 100644 --- a/panda/src/putil/pointerData.h +++ b/panda/src/putil/pointerData.h @@ -24,24 +24,29 @@ */ class EXPCL_PANDA_PUTIL PointerData { PUBLISHED: - INLINE PointerData(); - INLINE PointerData(const PointerData ©); - INLINE void operator = (const PointerData ©); - INLINE double get_x() const; INLINE double get_y() const; INLINE bool get_in_window() const; +public: + INLINE int get_id() const; + INLINE double get_pressure() const; + void output(std::ostream &out) const; +PUBLISHED: MAKE_PROPERTY(x, get_x); MAKE_PROPERTY(y, get_y); + MAKE_PROPERTY(id, get_id); MAKE_PROPERTY(in_window, get_in_window); + MAKE_PROPERTY(pressure, get_pressure); public: - bool _in_window; - double _xpos; - double _ypos; + bool _in_window = false; + double _xpos = 0.0; + double _ypos = 0.0; + double _pressure = 0.0; + int _id = 0; }; INLINE std::ostream &operator << (std::ostream &out, const PointerData &md); From aa750f4046755632678300d3f6d55636fcd4aac1 Mon Sep 17 00:00:00 2001 From: David Staer Date: Thu, 29 Nov 2018 23:02:40 +0800 Subject: [PATCH 297/424] Fix BulletCapsuleShape creation from Panda CollisionTube Functions get_point_a() and get_point_b() appear to return the top of the cylinder section of the tube, not the tip of the endcap, making subtracting the radius unnecessary. --- panda/src/bullet/bulletCapsuleShape.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/bullet/bulletCapsuleShape.cxx b/panda/src/bullet/bulletCapsuleShape.cxx index 8ff2b3e37a..0bdb1284dc 100644 --- a/panda/src/bullet/bulletCapsuleShape.cxx +++ b/panda/src/bullet/bulletCapsuleShape.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); From cc08bacec514d87038c37a8f7da0788fd722e7be Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 17:13:35 +0100 Subject: [PATCH 298/424] cocoa: fix compilation error --- panda/src/cocoadisplay/cocoaGraphicsWindow.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index 9801fe4341..df3ecf3b12 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -102,7 +102,7 @@ CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); _input_devices.push_back(device.p()); - _input = move(device); + _input = std::move(device); CocoaGraphicsPipe *cocoa_pipe; DCAST_INTO_V(cocoa_pipe, _pipe); From 2087d8ee84ee8cc051173787a81836c4a79a2295 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 17:25:23 +0100 Subject: [PATCH 299/424] device: change InputDevice enums to enum classes --- panda/src/device/clientDevice.cxx | 4 +- panda/src/device/clientDevice.h | 2 +- panda/src/device/clientTrackerDevice.I | 3 +- panda/src/device/evdevInputDevice.cxx | 94 +++++++++--------- panda/src/device/evdevInputDevice.h | 2 +- panda/src/device/inputDevice.I | 45 +++++---- panda/src/device/inputDevice.cxx | 51 ++++------ panda/src/device/inputDevice.h | 95 ++++++++++--------- panda/src/device/ioKitInputDevice.cxx | 48 +++++----- panda/src/device/linuxJoystickDevice.cxx | 20 ++-- panda/src/device/winRawInputDevice.cxx | 44 ++++----- panda/src/device/xInputDevice.cxx | 12 +-- panda/src/display/graphicsWindowInputDevice.I | 7 -- .../src/display/graphicsWindowInputDevice.cxx | 17 ++-- panda/src/display/graphicsWindowInputDevice.h | 5 +- 15 files changed, 223 insertions(+), 226 deletions(-) diff --git a/panda/src/device/clientDevice.cxx b/panda/src/device/clientDevice.cxx index 21a3a4d926..278977f30b 100644 --- a/panda/src/device/clientDevice.cxx +++ b/panda/src/device/clientDevice.cxx @@ -23,8 +23,8 @@ TypeHandle ClientDevice::_type_handle; */ ClientDevice:: ClientDevice(ClientBase *client, TypeHandle device_type, - const std::string &device_name, int device_flags) : - InputDevice(device_name, DC_unknown, device_flags), + const std::string &device_name) : + InputDevice(device_name, DeviceClass::unknown), _client(client), _device_type(device_type) { diff --git a/panda/src/device/clientDevice.h b/panda/src/device/clientDevice.h index e9ac1b30d3..b77d707484 100644 --- a/panda/src/device/clientDevice.h +++ b/panda/src/device/clientDevice.h @@ -27,7 +27,7 @@ class ClientBase; class EXPCL_PANDA_DEVICE ClientDevice : public InputDevice { protected: ClientDevice(ClientBase *client, TypeHandle device_type, - const std::string &device_name, int device_flags=0); + const std::string &device_name); public: virtual ~ClientDevice(); diff --git a/panda/src/device/clientTrackerDevice.I b/panda/src/device/clientTrackerDevice.I index 19c0596394..6ff99528e5 100644 --- a/panda/src/device/clientTrackerDevice.I +++ b/panda/src/device/clientTrackerDevice.I @@ -16,6 +16,7 @@ */ INLINE ClientTrackerDevice:: ClientTrackerDevice(ClientBase *client, const std::string &device_name): - ClientDevice(client, get_class_type(), device_name, IDF_has_tracker) + ClientDevice(client, get_class_type(), device_name) { + enable_feature(Feature::tracker); } diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 6fda488b0f..7c0dad8c82 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -66,27 +66,27 @@ static const struct DeviceMapping { int quirks; } mapping_presets[] = { // NVIDIA Shield Controller - {0x0955, 0x7214, InputDevice::DC_gamepad, QB_rstick_from_z}, + {0x0955, 0x7214, InputDevice::DeviceClass::gamepad, QB_rstick_from_z}, // T.Flight Hotas X - {0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle | QB_rudder_from_throttle}, + {0x044f, 0xb108, InputDevice::DeviceClass::flight_stick, QB_centered_throttle | QB_reversed_throttle | QB_rudder_from_throttle}, // Xbox 360 Wireless Controller - {0x045e, 0x0719, InputDevice::DC_gamepad, QB_connect_if_nonzero}, + {0x045e, 0x0719, InputDevice::DeviceClass::gamepad, QB_connect_if_nonzero}, // Jess Tech Colour Rumble Pad - {0x0f30, 0x0111, InputDevice::DC_gamepad, 0}, + {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0}, // 3Dconnexion Space Traveller 3D Mouse - {0x046d, 0xc623, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Pilot 3D Mouse - {0x046d, 0xc625, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc625, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Navigator 3D Mouse - {0x046d, 0xc626, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc626, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Explorer 3D Mouse - {0x046d, 0xc627, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc627, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Navigator for Notebooks - {0x046d, 0xc628, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc628, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion SpacePilot Pro 3D Mouse - {0x046d, 0xc629, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc629, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Mouse Pro - {0x046d, 0xc62b, InputDevice::DC_3d_mouse, 0}, + {0x046d, 0xc62b, InputDevice::DeviceClass::spatial_mouse, 0}, {0}, }; @@ -273,7 +273,7 @@ init_device() { has_keys = true; if (test_bit(KEY_A, keys) && test_bit(KEY_Z, keys)) { - _flags |= IDF_has_keyboard; + enable_feature(Feature::keyboard); } } @@ -298,38 +298,38 @@ init_device() { } // Try to detect which type of device we have here - if (_device_class == DC_unknown) { - int device_scores[DC_COUNT] = {0}; + if (_device_class == DeviceClass::unknown) { + int device_scores[(size_t)DeviceClass::spatial_mouse] = {0}; // Test for specific keys if (test_bit(BTN_GAMEPAD, keys) && test_bit(ABS_X, axes) && test_bit(ABS_RX, axes)) { - device_scores[DC_gamepad] += 5; - device_scores[DC_steering_wheel] += 5; - device_scores[DC_flight_stick] += 5; + device_scores[(size_t)DeviceClass::gamepad] += 5; + device_scores[(size_t)DeviceClass::steering_wheel] += 5; + device_scores[(size_t)DeviceClass::flight_stick] += 5; } if (test_bit(ABS_WHEEL, axes) && test_bit(ABS_GAS, axes) && test_bit(ABS_BRAKE, axes)) { - device_scores[DC_steering_wheel] += 10; + device_scores[(size_t)DeviceClass::steering_wheel] += 10; } if (test_bit(BTN_GEAR_DOWN, keys) && test_bit(BTN_GEAR_UP, keys)) { - device_scores[DC_steering_wheel] += 10; + device_scores[(size_t)DeviceClass::steering_wheel] += 10; } if (test_bit(BTN_JOYSTICK, keys) && test_bit(ABS_X, axes)) { - device_scores[DC_flight_stick] += 10; + device_scores[(size_t)DeviceClass::flight_stick] += 10; } if (test_bit(BTN_MOUSE, keys) && test_bit(EV_REL, evtypes)) { - device_scores[DC_mouse] += 20; + device_scores[(size_t)DeviceClass::mouse] += 20; } uint8_t unknown_keys[] = {KEY_POWER}; for (int i = 0; i < 1; i++) { if (test_bit(unknown_keys[i], keys)) { if (unknown_keys[i] == KEY_POWER) { } - device_scores[DC_unknown] += 20; + device_scores[(size_t)DeviceClass::unknown] += 20; } } - if (_flags & IDF_has_keyboard) { - device_scores[DC_keyboard] += 20; + if (_features & (unsigned int)Feature::keyboard) { + device_scores[(size_t)DeviceClass::keyboard] += 20; } // Test for specific name tags @@ -338,28 +338,28 @@ init_device() { lowercase_name[x] = tolower(lowercase_name[x]); } if (lowercase_name.find("gamepad") != string::npos) { - device_scores[DC_gamepad] += 10; + device_scores[(size_t)DeviceClass::gamepad] += 10; } if (lowercase_name.find("wheel") != string::npos) { - device_scores[DC_steering_wheel] += 10; + device_scores[(size_t)DeviceClass::steering_wheel] += 10; } if (lowercase_name.find("mouse") != string::npos || lowercase_name.find("touchpad") != string::npos) { - device_scores[DC_mouse] += 10; + device_scores[(size_t)DeviceClass::mouse] += 10; } if (lowercase_name.find("keyboard") != string::npos) { - device_scores[DC_keyboard] += 10; + device_scores[(size_t)DeviceClass::keyboard] += 10; } // List of lowercase names that occur in unknown devices string unknown_names[] = {"video bus", "power button", "sleep button"}; for(int i = 0; i < 3; i++) { if (lowercase_name.find(unknown_names[i]) != string::npos) { - device_scores[DC_unknown] += 20; + device_scores[(size_t)DeviceClass::unknown] += 20; } } // Check which device type got the most points int highest_score = 0; - for (size_t i = 0; i < DC_COUNT; i++) { + for (size_t i = 0; i < (size_t)DeviceClass::spatial_mouse; i++) { if (device_scores[i] > highest_score) { highest_score = device_scores[i]; _device_class = (DeviceClass)i; @@ -417,18 +417,18 @@ init_device() { Axis axis = Axis::none; switch (i) { case ABS_X: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_x; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = InputDevice::Axis::roll; } else { axis = InputDevice::Axis::x; } break; case ABS_Y: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_y; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = InputDevice::Axis::pitch; } else { axis = InputDevice::Axis::y; @@ -437,24 +437,24 @@ init_device() { case ABS_Z: if (quirks & QB_rstick_from_z) { axis = InputDevice::Axis::right_x; - } else if (_device_class == DC_gamepad) { + } else if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_trigger; have_analog_triggers = true; - } else if (_device_class == DC_3d_mouse) { + } else if (_device_class == DeviceClass::spatial_mouse) { axis = InputDevice::Axis::z; } else { axis = InputDevice::Axis::throttle; } break; case ABS_RX: - if (_device_class == DC_3d_mouse) { + if (_device_class == DeviceClass::spatial_mouse) { axis = InputDevice::Axis::pitch; } else if ((quirks & QB_rstick_from_z) == 0) { axis = InputDevice::Axis::right_x; } break; case ABS_RY: - if (_device_class == DC_3d_mouse) { + if (_device_class == DeviceClass::spatial_mouse) { axis = InputDevice::Axis::roll; } else if ((quirks & QB_rstick_from_z) == 0) { axis = InputDevice::Axis::right_y; @@ -463,7 +463,7 @@ init_device() { case ABS_RZ: if (quirks & QB_rstick_from_z) { axis = InputDevice::Axis::right_y; - } else if (_device_class == DC_gamepad) { + } else if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::right_trigger; have_analog_triggers = true; } else { @@ -484,7 +484,7 @@ init_device() { axis = InputDevice::Axis::wheel; break; case ABS_GAS: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::right_trigger; have_analog_triggers = true; } else { @@ -492,7 +492,7 @@ init_device() { } break; case ABS_BRAKE: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_trigger; have_analog_triggers = true; } else { @@ -503,7 +503,7 @@ init_device() { if (emulate_dpad) { _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { _buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_right())); } else { @@ -516,7 +516,7 @@ init_device() { if (emulate_dpad) { _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { _buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); } else { @@ -536,7 +536,7 @@ init_device() { // Also T.Flight Hotas X throttle is reversed and can go backwards. if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || (axis == Axis::throttle && (quirks & QB_reversed_throttle) != 0) || - (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { + (_device_class == DeviceClass::spatial_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { std::swap(absinfo.maximum, absinfo.minimum); } if (axis == Axis::throttle && (quirks & QB_centered_throttle) != 0) { @@ -556,7 +556,7 @@ init_device() { } if (test_bit(EV_REL, evtypes)) { - _flags |= IDF_has_pointer; + enable_feature(Feature::pointer); } if (test_bit(EV_FF, evtypes)) { @@ -565,7 +565,7 @@ init_device() { if (test_bit(FF_RUMBLE, effects)) { if (_can_write) { - _flags |= IDF_has_vibration; + enable_feature(Feature::vibration); } else { // Let the user know what he's missing out on. device_cat.warning() @@ -896,7 +896,7 @@ map_button(int code, DeviceClass device_class) { } } else if ((code & 0xfff0) == BTN_JOYSTICK) { - if (device_class == DC_gamepad) { + if (device_class == DeviceClass::gamepad) { // Based on "Jess Tech Colour Rumble Pad" static const ButtonHandle mapping[] = { GamepadButton::face_x(), diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 239ba78616..53c495ad08 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -64,7 +64,7 @@ private: int _rtrigger_code; public: - static ButtonHandle map_button(int code, DeviceClass device_class = DC_unknown); + static ButtonHandle map_button(int code, DeviceClass device_class = DeviceClass::unknown); public: static TypeHandle get_class_type() { diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index bcddc5cf52..aa8e069955 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -17,15 +17,7 @@ * */ INLINE InputDevice:: -InputDevice() : - _flags(0), - _device_class(DC_unknown), - _vendor_id(0), - _product_id(0), - _is_connected(false), - _event_sequence(0), - _enable_pointer_events(false) -{ +InputDevice() { _button_events = new ButtonEventList; } @@ -90,7 +82,7 @@ is_connected() const { /** * Returns an identification of the general type of device. If this could not - * be determined, returns DC_unknown. + * be determined, returns DeviceClass.unknown. */ INLINE InputDevice::DeviceClass InputDevice:: get_device_class() const { @@ -98,13 +90,21 @@ get_device_class() const { return _device_class; } +/** + * Returns true if the device supports the indicated feature. + */ +INLINE bool InputDevice:: +has_feature(Feature feature) const { + LightMutexHolder holder(_lock); + return (_features & (1 << (unsigned int)feature)) != 0; +} + /** * Returns true if this is a pointing device. */ INLINE bool InputDevice:: has_pointer() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_pointer) != 0); + return has_feature(Feature::pointer); } /** @@ -112,8 +112,7 @@ has_pointer() const { */ INLINE bool InputDevice:: has_keyboard() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_keyboard) != 0); + return has_feature(Feature::keyboard); } /** @@ -122,8 +121,7 @@ has_keyboard() const { */ INLINE bool InputDevice:: has_tracker() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_tracker) != 0); + return has_feature(Feature::tracker); } /** @@ -132,8 +130,7 @@ has_tracker() const { */ INLINE bool InputDevice:: has_vibration() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_vibration) != 0); + return has_feature(Feature::vibration); } /** @@ -142,8 +139,7 @@ has_vibration() const { */ INLINE bool InputDevice:: has_battery() const { - LightMutexHolder holder(_lock); - return ((_flags & IDF_has_battery) != 0); + return has_feature(Feature::battery); } /** @@ -393,6 +389,15 @@ disable_pointer_events() { _pointer_events.clear(); } +/** + * Called to indicate that the device supports the given feature. + * Assumes the lock is held. + */ +INLINE void InputDevice:: +enable_feature(Feature feature) { + _features |= (1 << (unsigned int)feature); +} + /** * Called to indicate that the device has been disconnected or connected from * its host. diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index 742c31b688..fc754d0d97 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -23,16 +23,10 @@ TypeHandle InputDevice::_type_handle; * Defines a new InputDevice. */ InputDevice:: -InputDevice(const std::string &name, DeviceClass dev_class, int flags) : +InputDevice(const std::string &name, DeviceClass dev_class) : _name(name), - _flags(flags), _device_class(dev_class), - _vendor_id(0), - _product_id(0), - _is_connected(true), - _event_sequence(0), - _enable_pointer_events(false), - _lock("InputDevice") + _is_connected(true) { _button_events = new ButtonEventList; } @@ -346,7 +340,7 @@ output(std::ostream &out) const { out << "connected)"; - if (_device_class != DC_unknown) { + if (_device_class != DeviceClass::unknown) { out << ", " << _device_class; } @@ -362,19 +356,19 @@ output(std::ostream &out) const { << (_axes.size() != 1 ? 'e' : 'i') << 's'; } - if (_flags & IDF_has_pointer) { + if (_features & (unsigned int)Feature::pointer) { out << ", pointer"; } - if (_flags & IDF_has_keyboard) { + if (_features & (unsigned int)Feature::keyboard) { out << ", keyboard"; } - if (_flags & IDF_has_tracker) { + if (_features & (unsigned int)Feature::tracker) { out << ", tracker"; } - if (_flags & IDF_has_vibration) { + if (_features & (unsigned int)Feature::vibration) { out << ", vibration"; } - if (_flags & IDF_has_battery) { + if (_features & (unsigned int)Feature::battery) { out << ", battery"; if (_battery_data.level > 0 && _battery_data.max_level > 0) { @@ -504,41 +498,38 @@ do_poll() { std::string InputDevice:: format_device_class(DeviceClass dc) { switch (dc) { - case InputDevice::DC_unknown: + case InputDevice::DeviceClass::unknown: return "unknown"; - case InputDevice::DC_virtual: - return "virtual"; + case InputDevice::DeviceClass::virtual_device: + return "virtual_device"; - case InputDevice::DC_keyboard: + case InputDevice::DeviceClass::keyboard: return "keyboard"; - case InputDevice::DC_mouse: + case InputDevice::DeviceClass::mouse: return "mouse"; - case InputDevice::DC_touch: + case InputDevice::DeviceClass::touch: return "touch"; - case InputDevice::DC_gamepad: + case InputDevice::DeviceClass::gamepad: return "gamepad"; - case InputDevice::DC_flight_stick: + case InputDevice::DeviceClass::flight_stick: return "flight_stick"; - case InputDevice::DC_steering_wheel: + case InputDevice::DeviceClass::steering_wheel: return "steering_wheel"; - case InputDevice::DC_dance_pad: + case InputDevice::DeviceClass::dance_pad: return "dance_pad"; - case InputDevice::DC_hmd: + case InputDevice::DeviceClass::hmd: return "hmd"; - case InputDevice::DC_3d_mouse: - return "3d_mouse"; - - case InputDevice::DC_COUNT: - break; + case InputDevice::DeviceClass::spatial_mouse: + return "spatial_mouse"; } return "**invalid**"; } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 24b27186e3..0088ea8f86 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -52,35 +52,49 @@ class EXPCL_PANDA_DEVICE InputDevice : public TypedReferenceCount { PUBLISHED: // This enum contains information that can be used to identify the // type of input device. - enum DeviceClass { + enum class DeviceClass { // It is not known what type of device this is. - DC_unknown, + unknown, // This means that the device doesn't correspond to a physical // device, but rather to a dynamic source of input events. - DC_virtual, + virtual_device, // A physical, alphabetical keyboard. - DC_keyboard, + keyboard, - DC_mouse, - DC_touch, + mouse, + touch, // A gamepad with action buttons, a D-pad, and thumbsticks. - DC_gamepad, + gamepad, - DC_flight_stick, - DC_steering_wheel, - DC_dance_pad, + flight_stick, + steering_wheel, + dance_pad, // Head-mounted display. - DC_hmd, + hmd, // 3D mouse, such as produced by 3Dconnexion. - DC_3d_mouse, + spatial_mouse, + }; - // Count of this enum, used for loops - DC_COUNT, + enum class Feature { + // The device provides absolute screen coordinates. + pointer, + + // The device has an interface for providing text input. + keyboard, + + // The device has a motion tracker, such as an HMD. + tracker, + + // The device can produce force feedback. + vibration, + + // The device provides information about battery life. + battery, }; enum class Axis { @@ -161,7 +175,7 @@ PUBLISHED: }; protected: - InputDevice(const std::string &name, DeviceClass dev_class, int flags); + InputDevice(const std::string &name, DeviceClass dev_class); public: InputDevice(); @@ -177,6 +191,12 @@ public: INLINE bool is_connected() const; INLINE DeviceClass get_device_class() const; + INLINE bool has_pointer() const; + INLINE bool has_keyboard() const; + INLINE bool has_tracker() const; + INLINE bool has_vibration() const; + INLINE bool has_battery() const; + INLINE PointerData get_pointer() const; INLINE TrackerData get_tracker() const; INLINE BatteryData get_battery() const; @@ -216,11 +236,8 @@ PUBLISHED: // this could not be determined, it is set to DC_unknown. MAKE_PROPERTY(device_class, get_device_class); - INLINE bool has_pointer() const; - INLINE bool has_keyboard() const; - INLINE bool has_tracker() const; - INLINE bool has_vibration() const; - INLINE bool has_battery() const; + // Determine supported features + INLINE bool has_feature(Feature feature) const; // Getters for the various types of device data. MAKE_PROPERTY2(pointer, has_pointer, get_pointer); @@ -255,6 +272,8 @@ PUBLISHED: static std::string format_axis(Axis axis); protected: + INLINE void enable_feature(Feature feature); + // Called during the constructor to add new axes or buttons int add_button(ButtonHandle handle); int add_axis(Axis axis, int minimum, int maximum, bool centered); @@ -280,40 +299,21 @@ public: void write_buttons(std::ostream &out, int indent_level) const; void write_axes(std::ostream &out, int indent_level) const; -protected: - enum InputDeviceFlags { - // The device provides absolute screen coordinates. - IDF_has_pointer = 0x01, - - // The device has an interface for providing text input. - IDF_has_keyboard = 0x02, - - // The device has a motion tracker, such as an HMD. - IDF_has_tracker = 0x04, - - // The device can produce force feedback. - IDF_has_vibration = 0x08, - - // The device provides information about battery life. - IDF_has_battery = 0x10, - }; - protected: typedef pdeque ButtonEvents; - LightMutex _lock; + LightMutex _lock {"InputDevice"}; std::string _name; std::string _serial_number; std::string _manufacturer; - DeviceClass _device_class; - int _flags; - int _event_sequence; - unsigned short _vendor_id; - unsigned short _product_id; - bool _is_connected; - bool _enable_pointer_events; - PointerData _pointer_data; + DeviceClass _device_class = DeviceClass::unknown; + unsigned int _features = 0; + int _event_sequence = 0; + unsigned short _vendor_id = 0; + unsigned short _product_id = 0; + bool _is_connected = false; + bool _enable_pointer_events = false; PT(ButtonEventList) _button_events; PT(PointerEventList) _pointer_events; @@ -323,6 +323,7 @@ PUBLISHED: Buttons _buttons; Axes _axes; + PointerData _pointer_data; BatteryData _battery_data; TrackerData _tracker_data; diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index b3505e084f..e139a314e5 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/panda/src/device/ioKitInputDevice.cxx @@ -84,18 +84,18 @@ IOKitInputDevice(IOHIDDeviceRef device) : } if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse)) { - _device_class = DC_mouse; + _device_class = DeviceClass::mouse; } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) { - _device_class = DC_keyboard; + _device_class = DeviceClass::keyboard; } else if (IOHIDDeviceConformsTo(device, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad)) { - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_Sim_FlightStick)) { - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; } else if (IOHIDDeviceConformsTo(device, kHIDPage_Simulation, kHIDUsage_GD_Joystick)) { - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; } else if (_vendor_id == 0x044f && _product_id == 0xb108) { // T.Flight Hotas X - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; } else if (_vendor_id == 0x046d && (_product_id == 0xc623 || _product_id == 0xc625 || @@ -105,9 +105,9 @@ IOKitInputDevice(IOHIDDeviceRef device) : _product_id == 0xc629 || _product_id == 0xc62b)) { // 3Dconnexion SpaceNavigator and friends. - _device_class = DC_3d_mouse; + _device_class = DeviceClass::spatial_mouse; } else if (_name == "usb gamepad") { - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; } CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, 0); @@ -127,7 +127,7 @@ IOKitInputDevice(IOHIDDeviceRef device) : } if (_pointer_x != nullptr && _pointer_y != nullptr) { - _flags |= IDF_has_pointer; + enable_feature(Feature::pointer); } _is_connected = true; @@ -182,11 +182,11 @@ parse_element(IOHIDElementRef element) { case kHIDPage_GenericDesktop: switch (usage) { case kHIDUsage_GD_X: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_x; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::roll; - } else if (_device_class == DC_mouse) { + } else if (_device_class == DeviceClass::mouse) { _pointer_x = element; return; } else { @@ -194,11 +194,11 @@ parse_element(IOHIDElementRef element) { } break; case kHIDUsage_GD_Y: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_y; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::pitch; - } else if (_device_class == DC_mouse) { + } else if (_device_class == DeviceClass::mouse) { _pointer_y = element; return; } else { @@ -206,30 +206,30 @@ parse_element(IOHIDElementRef element) { } break; case kHIDUsage_GD_Z: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_trigger; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::throttle; } else { axis = Axis::z; } break; case kHIDUsage_GD_Rx: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_x; } else { axis = Axis::pitch; } break; case kHIDUsage_GD_Ry: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_y; } else { axis = Axis::roll; } break; case kHIDUsage_GD_Rz: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_trigger; } else { axis = Axis::yaw; @@ -289,7 +289,7 @@ parse_element(IOHIDElementRef element) { // T.Flight Hotas X throttle is reversed and can go backwards. add_axis(axis, max, min, true); } else if (axis == Axis::yaw || axis == Axis::rudder || axis == Axis::left_y || axis == Axis::right_y || - (_device_class == DC_3d_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { + (_device_class == DeviceClass::spatial_mouse && (axis == Axis::y || axis == Axis::z || axis == Axis::roll))) { // We'd like to reverse the Y axis to match the XInput behavior. // We also reverse yaw to obey the right-hand rule. add_axis(axis, max, min); @@ -627,7 +627,7 @@ parse_element(IOHIDElementRef element) { break; case kHIDPage_Button: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { if (_vendor_id == 0x0810 && _product_id == 0xe501) { // SNES-style USB gamepad static const ButtonHandle gamepad_buttons[] = { @@ -671,11 +671,11 @@ parse_element(IOHIDElementRef element) { handle = gamepad_buttons[usage]; } } - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { if (usage > 0) { handle = GamepadButton::joystick(usage - 1); } - } else if (_device_class == DC_mouse) { + } else if (_device_class == DeviceClass::mouse) { // In Panda, wheel and right button are flipped around... int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); handle = MouseButton::button(button); diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index ccdd29d553..1871a1e948 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -135,9 +135,9 @@ open_device() { << " button " << (int)i << ": 0x" << std::hex << btnmap[i] << "\n"; } } else if (handle == GamepadButton::face_a()) { - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; } else if (handle == GamepadButton::trigger()) { - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; } else if (handle == GamepadButton::ltrigger()) { _ltrigger_button = i; } else if (handle == GamepadButton::rtrigger()) { @@ -156,9 +156,9 @@ open_device() { switch (axmap[i]) { case ABS_X: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_x; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = InputDevice::Axis::roll; } else { axis = InputDevice::Axis::x; @@ -166,9 +166,9 @@ open_device() { break; case ABS_Y: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::left_y; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = InputDevice::Axis::pitch; } else { axis = InputDevice::Axis::y; @@ -176,7 +176,7 @@ open_device() { break; case ABS_Z: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_trigger; } else { //axis = Axis::trigger; @@ -192,7 +192,7 @@ open_device() { break; case ABS_RZ: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = InputDevice::Axis::right_trigger; } else { axis = InputDevice::Axis::yaw; @@ -224,7 +224,7 @@ open_device() { // Emulate D-Pad or hat switch. _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { add_button(GamepadButton::dpad_left()); add_button(GamepadButton::dpad_right()); } else { @@ -240,7 +240,7 @@ open_device() { // Emulate D-Pad. _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { add_button(GamepadButton::dpad_up()); add_button(GamepadButton::dpad_down()); } else { diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index 7aa520a1fe..1af01cd4b4 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -244,15 +244,15 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { LightMutexHolder holder(_lock); - _name = move(name); + _name = std::move(name); switch (info.dwType) { case RIM_TYPEMOUSE: - _device_class = DC_mouse; + _device_class = DeviceClass::mouse; break; case RIM_TYPEKEYBOARD: - _device_class = DC_keyboard; + _device_class = DeviceClass::keyboard; break; case RIM_TYPEHID: @@ -262,27 +262,27 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // Gamepads if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_GAMEPAD) { - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; // Flight sticks } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) { - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; if (_name == "usb gamepad") { // Well, it claims to be a gamepad... - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; } // Mice } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) { - _device_class = DC_mouse; + _device_class = DeviceClass::mouse; // Keyboards } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_KEYBOARD) { - _device_class = DC_keyboard; + _device_class = DeviceClass::keyboard; // 3Dconnexion SpaceNavigator and friends. } else if (_vendor_id == 0x046d && @@ -293,7 +293,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { _product_id == 0xc628 || _product_id == 0xc629 || _product_id == 0xc62b)) { - _device_class = DC_3d_mouse; + _device_class = DeviceClass::spatial_mouse; } break; @@ -410,15 +410,15 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { ButtonHandle handle = ButtonHandle::none(); switch (cap.UsagePage) { case HID_USAGE_PAGE_BUTTON: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { if (usage < sizeof(gamepad_buttons_common) / sizeof(ButtonHandle)) { handle = gamepad_buttons[usage]; } - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { if (usage > 0) { handle = GamepadButton::joystick(usage - 1); } - } else if (_device_class == DC_mouse) { + } else if (_device_class == DeviceClass::mouse) { // In Panda, wheel and right button are flipped around... int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1); handle = MouseButton::button(button); @@ -483,19 +483,19 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { case HID_USAGE_PAGE_GENERIC: switch (usage) { case HID_USAGE_GENERIC_X: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_x; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::roll; } else { axis = Axis::x; } break; case HID_USAGE_GENERIC_Y: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_y; swap(cap.LogicalMin, cap.LogicalMax); - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::pitch; } else { axis = Axis::y; @@ -503,9 +503,9 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { } break; case HID_USAGE_GENERIC_Z: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::left_trigger; - } else if (_device_class == DC_flight_stick) { + } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::throttle; } else { axis = Axis::z; @@ -513,14 +513,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { } break; case HID_USAGE_GENERIC_RX: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_x; } else { axis = Axis::pitch; } break; case HID_USAGE_GENERIC_RY: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_y; } else { axis = Axis::roll; @@ -528,7 +528,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { swap(cap.LogicalMin, cap.LogicalMax); break; case HID_USAGE_GENERIC_RZ: - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { axis = Axis::right_trigger; } else { // Flip to match Panda's convention for heading. @@ -570,7 +570,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // Do we need to emulate a hat switch or directional pad? if (_hat_data_index != -1) { _hat_left_button = (int)_buttons.size(); - if (_device_class == DC_gamepad) { + if (_device_class == DeviceClass::gamepad) { _buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_right())); _buttons.push_back(ButtonState(GamepadButton::dpad_down())); diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 40cb56959c..42f9c4fbef 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -315,7 +315,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { switch (caps.SubType) { default: case XINPUT_DEVSUBTYPE_GAMEPAD: - _device_class = DC_gamepad; + _device_class = DeviceClass::gamepad; _axes[0].axis = Axis::left_trigger; _axes[1].axis = Axis::right_trigger; _axes[2].axis = Axis::left_x; @@ -325,7 +325,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { break; case XINPUT_DEVSUBTYPE_WHEEL: - _device_class = DC_steering_wheel; + _device_class = DeviceClass::steering_wheel; _axes[0].axis = Axis::brake; _axes[1].axis = Axis::accelerator; _axes[2].axis = Axis::wheel; @@ -335,7 +335,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { break; case XINPUT_DEVSUBTYPE_FLIGHT_STICK: - _device_class = DC_flight_stick; + _device_class = DeviceClass::flight_stick; _axes[0].axis = Axis::yaw; _axes[1].axis = Axis::throttle; _axes[2].axis = Axis::roll; @@ -345,7 +345,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { break; case XINPUT_DEVSUBTYPE_DANCE_PAD: - _device_class = DC_dance_pad; + _device_class = DeviceClass::dance_pad; _axes[0].axis = Axis::none; _axes[1].axis = Axis::none; _axes[2].axis = Axis::none; @@ -394,7 +394,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { if (caps.Vibration.wLeftMotorSpeed != 0 || caps.Vibration.wRightMotorSpeed != 0) { - _flags |= IDF_has_vibration; + enable_feature(Feature::vibration); } if (get_battery_information != nullptr) { @@ -403,7 +403,7 @@ init_device(const XINPUT_CAPABILITIES_EX &caps, const XINPUT_STATE &state) { if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED && batt.BatteryType != BATTERY_TYPE_WIRED) { // This device has a battery. Report the battery level. - _flags |= IDF_has_battery; + enable_feature(Feature::battery); _battery_data.level = batt.BatteryLevel; _battery_data.max_level = BATTERY_LEVEL_FULL; } diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index c53af656f8..cba6bfdb30 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -11,13 +11,6 @@ * @date 2000-05-24 */ -/** - * - */ -INLINE GraphicsWindowInputDevice:: -GraphicsWindowInputDevice() { -} - /** * To be called by a particular kind of GraphicsWindow to indicate that the * pointer is within the window, at the given pixel coordinates. diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index 3d7102f06f..b747c64b3c 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -29,9 +29,15 @@ using std::string; * below. */ GraphicsWindowInputDevice:: -GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : - InputDevice(name, DC_virtual, flags) +GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, bool pointer, bool keyboard) : + InputDevice(name, DeviceClass::virtual_device) { + if (pointer) { + enable_feature(Feature::pointer); + } + if (keyboard) { + enable_feature(Feature::keyboard); + } } /** @@ -40,7 +46,7 @@ GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, int flags) : */ PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: pointer_only(GraphicsWindow *host, const string &name) { - return new GraphicsWindowInputDevice(host, name, IDF_has_pointer); + return new GraphicsWindowInputDevice(host, name, true, false); } /** @@ -49,7 +55,7 @@ pointer_only(GraphicsWindow *host, const string &name) { */ PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: keyboard_only(GraphicsWindow *host, const string &name) { - return new GraphicsWindowInputDevice(host, name, IDF_has_keyboard); + return new GraphicsWindowInputDevice(host, name, false, true); } /** @@ -58,8 +64,7 @@ keyboard_only(GraphicsWindow *host, const string &name) { */ PT(GraphicsWindowInputDevice) GraphicsWindowInputDevice:: pointer_and_keyboard(GraphicsWindow *host, const string &name) { - return new - GraphicsWindowInputDevice(host, name, IDF_has_pointer | IDF_has_keyboard); + return new GraphicsWindowInputDevice(host, name, true, true); } /** diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index 1f9d941add..aa3d97cd7b 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -27,14 +27,15 @@ class GraphicsWindow; */ class EXPCL_PANDA_DISPLAY GraphicsWindowInputDevice : public InputDevice { private: - GraphicsWindowInputDevice(GraphicsWindow *host, const std::string &name, int flags); + GraphicsWindowInputDevice(GraphicsWindow *host, const std::string &name, + bool pointer, bool keyboard); public: static PT(GraphicsWindowInputDevice) pointer_only(GraphicsWindow *host, const std::string &name); static PT(GraphicsWindowInputDevice) keyboard_only(GraphicsWindow *host, const std::string &name); static PT(GraphicsWindowInputDevice) pointer_and_keyboard(GraphicsWindow *host, const std::string &name); - INLINE GraphicsWindowInputDevice(); + GraphicsWindowInputDevice() = default; PUBLISHED: // The following interface is for the various kinds of GraphicsWindows to From 29170278e97cd8136bee3a24e003e1f35e613e61 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 19:52:26 +0100 Subject: [PATCH 300/424] device: a few InputDevice API tweaks find_button() and find_axis() should not error since there is no way to find out whether a button/axis exists or not --- panda/src/device/inputDevice.I | 22 ++++------------------ panda/src/device/inputDevice.h | 6 ++++++ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index aa8e069955..fc5236594c 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -250,13 +250,8 @@ is_button_known(size_t index) const { */ INLINE InputDevice::ButtonState InputDevice:: get_button(size_t index) const { - if (index < _buttons.size()) { - return _buttons[index]; - } else { - device_cat.error() - << "Index " << index << " was not found in the axes list\n"; - return ButtonState(); - } + nassertr_always(index < _buttons.size(), ButtonState()); + return _buttons[index]; } /** @@ -270,8 +265,6 @@ find_button(ButtonHandle handle) const { return _buttons[i]; } } - device_cat.error() - << "Handle " << handle.get_name() << " was not found in the axes list\n"; return ButtonState(); } @@ -321,13 +314,8 @@ get_axis_value(size_t index) const { */ INLINE InputDevice::AxisState InputDevice:: get_axis(size_t index) const { - if (index < _axes.size()) { - return _axes[index]; - } else { - device_cat.error() - << "Index " << index << " was not found in the axes list\n"; - return AxisState(); - } + nassertr_always(index < _axes.size(), AxisState()); + return _axes[index]; } /** @@ -341,8 +329,6 @@ find_axis(InputDevice::Axis axis) const { return _axes[i]; } } - device_cat.error() - << "Axis " << axis << " was not found in the axes list\n"; return AxisState(); } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 0088ea8f86..25bcfea9c0 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -142,6 +142,8 @@ PUBLISHED: ALWAYS_INLINE bool is_pressed() const; PUBLISHED: + operator bool() { return _state != S_unknown; } + MAKE_PROPERTY(known, is_known); MAKE_PROPERTY(pressed, is_pressed); @@ -156,6 +158,8 @@ PUBLISHED: constexpr AxisState() = default; PUBLISHED: + operator bool() { return known && value != 0.0; } + Axis axis = Axis::none; double value = 0.0; bool known = false; @@ -268,6 +272,8 @@ PUBLISHED: PT(PointerEventList) get_pointer_events(); virtual void output(std::ostream &out) const; + +public: static std::string format_device_class(DeviceClass dc); static std::string format_axis(Axis axis); From 447316c7065bd1bf3a2845eefea2d5f89ec7b677 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 20:13:13 +0100 Subject: [PATCH 301/424] samples: fixes for gamepad samples --- samples/gamepad/flightstick.py | 6 +++--- samples/gamepad/gamepad.py | 6 +++--- samples/gamepad/mappingGUI.py | 6 ++++-- samples/gamepad/steeringWheel.py | 6 +++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/samples/gamepad/flightstick.py b/samples/gamepad/flightstick.py index 0a06c52585..4520cfefb2 100644 --- a/samples/gamepad/flightstick.py +++ b/samples/gamepad/flightstick.py @@ -38,7 +38,7 @@ class App(ShowBase): # Is there a gamepad connected? self.flightStick = None - devices = self.devices.getDevices(InputDevice.DC_flight_stick) + devices = self.devices.getDevices(InputDevice.DeviceClass.flight_stick) if devices: self.connect(devices[0]) @@ -74,7 +74,7 @@ class App(ShowBase): # We're only interested if this is a flight stick and we don't have a # flight stick yet. - if device.device_class == InputDevice.DC_flight_stick and not self.flightStick: + if device.device_class == InputDevice.DeviceClass.flight_stick and not self.flightStick: print("Found %s" % (device)) self.flightStick = device @@ -98,7 +98,7 @@ class App(ShowBase): self.flightStick = None # Do we have any other gamepads? Attach the first other gamepad. - devices = self.devices.getDevices(InputDevice.DC_flight_stick) + devices = self.devices.getDevices(InputDevice.DeviceClass.flight_stick) if devices: self.connect(devices[0]) else: diff --git a/samples/gamepad/gamepad.py b/samples/gamepad/gamepad.py index c684c01253..8679a74187 100644 --- a/samples/gamepad/gamepad.py +++ b/samples/gamepad/gamepad.py @@ -63,7 +63,7 @@ class App(ShowBase): # Is there a gamepad connected? self.gamepad = None - devices = self.devices.getDevices(InputDevice.DC_gamepad) + devices = self.devices.getDevices(InputDevice.DeviceClass.gamepad) if devices: self.connect(devices[0]) @@ -99,7 +99,7 @@ class App(ShowBase): # We're only interested if this is a gamepad and we don't have a # gamepad yet. - if device.device_class == InputDevice.DC_gamepad and not self.gamepad: + if device.device_class == InputDevice.DeviceClass.gamepad and not self.gamepad: print("Found %s" % (device)) self.gamepad = device @@ -123,7 +123,7 @@ class App(ShowBase): self.gamepad = None # Do we have any other gamepads? Attach the first other gamepad. - devices = self.devices.getDevices(InputDevice.DC_gamepad) + devices = self.devices.getDevices(InputDevice.DeviceClass.gamepad) if devices: self.connect(devices[0]) else: diff --git a/samples/gamepad/mappingGUI.py b/samples/gamepad/mappingGUI.py index 84f26a9f60..17896c33a6 100644 --- a/samples/gamepad/mappingGUI.py +++ b/samples/gamepad/mappingGUI.py @@ -83,6 +83,8 @@ class ChangeActionDialog(object): self.__command = command + self.attachedDevices = [] + # Initialize the DirectGUI stuff. self.dialog = OkCancelDialog( dialogName="dlg_device_input", @@ -293,7 +295,7 @@ class MappingGUIDemo(ShowBase): devices = base.devices.getDevices() for device in devices: base.attachInputDevice(device) - self.attachedDevices = devices + self.attachedDevices = devices # Disable regular button events on all button event throwers, and # instead broadcast a generic event. @@ -324,7 +326,7 @@ class MappingGUIDemo(ShowBase): def watchControls(self, task): # move through all devices and all it's controls for device in self.attachedDevices: - if device.device_class == InputDevice.DC_mouse: + if device.device_class == InputDevice.DeviceClass.mouse: # Ignore mouse axis movement, or the user can't even navigate # to the OK/Cancel buttons! continue diff --git a/samples/gamepad/steeringWheel.py b/samples/gamepad/steeringWheel.py index 65e5aa63f4..09b0d9d167 100644 --- a/samples/gamepad/steeringWheel.py +++ b/samples/gamepad/steeringWheel.py @@ -35,7 +35,7 @@ class App(ShowBase): # Is there a steering wheel connected? self.wheel = None - devices = self.devices.getDevices(InputDevice.DC_steering_wheel) + devices = self.devices.getDevices(InputDevice.DeviceClass.steering_wheel) if devices: self.connect(devices[0]) @@ -79,7 +79,7 @@ class App(ShowBase): # We're only interested if this is a steering wheel and we don't have a # wheel yet. - if device.device_class == InputDevice.DC_steering_wheel and not self.wheel: + if device.device_class == InputDevice.DeviceClass.steering_wheel and not self.wheel: print("Found %s" % (device)) self.wheel = device @@ -103,7 +103,7 @@ class App(ShowBase): self.wheel = None # Do we have any steering wheels? Attach the first other steering wheel. - devices = self.devices.getDevices(InputDevice.DC_steering_wheel) + devices = self.devices.getDevices(InputDevice.DeviceClass.steering_wheel) if devices: self.connect(devices[0]) else: From 585ba2d647a34c90ff7aeb0f2e9717db119bb27a Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 30 Nov 2018 20:29:20 +0100 Subject: [PATCH 302/424] Prepare input API for multipointer support --- panda/src/device/evdevInputDevice.cxx | 15 +-- panda/src/device/inputDevice.I | 11 +- panda/src/device/inputDevice.cxx | 122 ++++++++++++------ panda/src/device/inputDevice.h | 14 +- panda/src/display/graphicsWindow.cxx | 2 +- panda/src/display/graphicsWindowInputDevice.I | 32 +++-- .../src/display/graphicsWindowInputDevice.cxx | 43 ++++++ panda/src/display/graphicsWindowInputDevice.h | 7 +- panda/src/display/mouseAndKeyboard.cxx | 24 +--- panda/src/display/mouseAndKeyboard.h | 5 +- panda/src/event/pointerEvent.I | 18 --- panda/src/event/pointerEvent.h | 27 ++-- panda/src/event/pointerEventList.I | 16 +++ panda/src/event/pointerEventList.cxx | 36 ++++++ panda/src/event/pointerEventList.h | 4 + panda/src/putil/pointerData.I | 8 ++ panda/src/putil/pointerData.h | 16 +++ panda/src/tform/mouseWatcher.I | 2 +- panda/src/tform/mouseWatcher.cxx | 2 +- panda/src/tform/mouseWatcher.h | 4 +- panda/src/windisplay/winGraphicsWindow.cxx | 2 +- panda/src/x11display/x11GraphicsWindow.cxx | 2 +- 22 files changed, 281 insertions(+), 131 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 7c0dad8c82..a5c916d8bc 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -557,6 +557,7 @@ init_device() { if (test_bit(EV_REL, evtypes)) { enable_feature(Feature::pointer); + add_pointer(PointerType::unknown, 0); } if (test_bit(EV_FF, evtypes)) { @@ -669,9 +670,8 @@ process_events() { n_read /= sizeof(struct input_event); - int x = _pointer_data.get_x(); - int y = _pointer_data.get_y(); - bool have_pointer = false; + int rel_x = 0; + int rel_y = 0; double time = ClockObject::get_global_clock()->get_frame_time(); int index; @@ -689,9 +689,8 @@ process_events() { break; case EV_REL: - if (code == REL_X) x += events[i].value; - if (code == REL_Y) y += events[i].value; - have_pointer = true; + if (code == REL_X) rel_x += events[i].value; + if (code == REL_Y) rel_y += events[i].value; break; case EV_ABS: @@ -728,8 +727,8 @@ process_events() { } } - if (have_pointer) { - set_pointer(true, x, y, time); + if (rel_x != 0 || rel_y != 0) { + pointer_moved(0, rel_x, rel_y, time); } return true; diff --git a/panda/src/device/inputDevice.I b/panda/src/device/inputDevice.I index fc5236594c..532e77f796 100644 --- a/panda/src/device/inputDevice.I +++ b/panda/src/device/inputDevice.I @@ -19,6 +19,7 @@ INLINE InputDevice:: InputDevice() { _button_events = new ButtonEventList; + _pointer_events = new PointerEventList; } /** @@ -142,16 +143,6 @@ has_battery() const { return has_feature(Feature::battery); } -/** - * Returns the PointerData associated with the input device's pointer. This - * only makes sense if has_pointer() also returns true. - */ -INLINE PointerData InputDevice:: -get_pointer() const { - LightMutexHolder holder(_lock); - return _pointer_data; -} - /** * Returns the TrackerData associated with the input device's tracker. This * only makes sense if has_tracker() also returns true. diff --git a/panda/src/device/inputDevice.cxx b/panda/src/device/inputDevice.cxx index fc754d0d97..6f5ede4f9a 100644 --- a/panda/src/device/inputDevice.cxx +++ b/panda/src/device/inputDevice.cxx @@ -29,6 +29,7 @@ InputDevice(const std::string &name, DeviceClass dev_class) : _is_connected(true) { _button_events = new ButtonEventList; + _pointer_events = new PointerEventList; } /** @@ -80,7 +81,7 @@ get_button_events() { bool InputDevice:: has_pointer_event() const { LightMutexHolder holder(_lock); - return (_pointer_events != 0); + return _pointer_events != nullptr && !_pointer_events->empty(); } /** @@ -90,8 +91,8 @@ has_pointer_event() const { PT(PointerEventList) InputDevice:: get_pointer_events() { LightMutexHolder holder(_lock); - PT(PointerEventList) result = _pointer_events; - _pointer_events.clear(); + PT(PointerEventList) result = new PointerEventList; + swap(_pointer_events, result); return result; } @@ -149,69 +150,116 @@ add_axis(Axis axis, int minimum, int maximum) { } /** - * Records that a mouse movement has taken place. + * Records that a new pointer was found. + */ +int InputDevice:: +add_pointer(PointerType type, int id, bool primary) { + nassertr(_lock.debug_is_locked(), -1); + + PointerData data; + data._id = id; + data._type = type; + + int index = (int)_pointers.size(); + if (_num_pointers == _pointers.size()) { + _pointers.push_back(data); + } else { + _pointers[index] = data; + } + ++_num_pointers; + + return index; +} + +/** + * Removes a previously added pointer. If the current pressure is not zero, + * it will generate an event doing so. */ void InputDevice:: -set_pointer(bool inwin, double x, double y, double time) { +remove_pointer(int id) { nassertv(_lock.debug_is_locked()); - _pointer_data._in_window = inwin; - _pointer_data._xpos = x; - _pointer_data._ypos = y; - if (_enable_pointer_events) { - int seq = _event_sequence++; - if (_pointer_events.is_null()) { - _pointer_events = new PointerEventList(); + size_t i; + for (i = 0; i < _pointers.size(); ++i) { + if (_pointers[i]._id == id) { + break; } - _pointer_events->add_event(_pointer_data._in_window, - _pointer_data._xpos, - _pointer_data._ypos, - seq, time); + } + + if (i < _pointers.size()) { + if (_pointers[i]._pressure != 0.0) { + _pointers[i]._pressure = 0.0; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + double time = ClockObject::get_global_clock()->get_frame_time(); + _pointer_events->add_event(_pointers[i], seq, time); + } + } + + // Replace it with the last one. + if (i != _pointers.size() - 1) { + _pointers[i] = _pointers.back(); + } + --_num_pointers; } } /** - * Records that the mouse pointer has left the window. + * Records that pointer data for a pointer has changed. This can also be used + * to add a new pointer. */ void InputDevice:: -set_pointer_out_of_window(double time) { +update_pointer(PointerData data, double time) { nassertv(_lock.debug_is_locked()); - _pointer_data._in_window = false; + + PointerData *ptr = nullptr; + for (size_t i = 0; i < _pointers.size(); ++i) { + if (_pointers[i]._id == data._id) { + ptr = &_pointers[i]; + *ptr = data; + break; + } + } + if (ptr == nullptr) { + _pointers.push_back(data); + ptr = &_pointers.back(); + } if (_enable_pointer_events) { int seq = _event_sequence++; - if (_pointer_events.is_null()) { - _pointer_events = new PointerEventList(); - } - _pointer_events->add_event(_pointer_data._in_window, - _pointer_data._xpos, - _pointer_data._ypos, - seq, time); + _pointer_events->add_event(*ptr, seq, time); } } /** - * Records that a relative mouse movement has taken place. + * Records that a relative pointer movement has taken place. */ void InputDevice:: -pointer_moved(double x, double y, double time) { +pointer_moved(int id, double x, double y, double time) { nassertv(_lock.debug_is_locked()); - _pointer_data._xpos += x; - _pointer_data._ypos += y; + + PointerData *ptr = nullptr; + for (size_t i = 0; i < _pointers.size(); ++i) { + if (_pointers[i]._id == id) { + ptr = &_pointers[i]; + _pointers[i]._xpos = x; + _pointers[i]._ypos = y; + break; + } + } + nassertv_always(ptr != nullptr); if (device_cat.is_spam() && (x != 0 || y != 0)) { device_cat.spam() - << "Pointer moved by " << x << " x " << y << "\n"; + << "Pointer " << id << " moved by " << x << " x " << y << "\n"; } if (_enable_pointer_events) { int seq = _event_sequence++; - if (_pointer_events.is_null()) { - _pointer_events = new PointerEventList(); - } - _pointer_events->add_event(_pointer_data._in_window, - _pointer_data._xpos, - _pointer_data._ypos, + _pointer_events->add_event(ptr->_in_window, + ptr->_xpos, + ptr->_ypos, x, y, seq, time); } } diff --git a/panda/src/device/inputDevice.h b/panda/src/device/inputDevice.h index 25bcfea9c0..3e4e37313e 100644 --- a/panda/src/device/inputDevice.h +++ b/panda/src/device/inputDevice.h @@ -201,7 +201,6 @@ public: INLINE bool has_vibration() const; INLINE bool has_battery() const; - INLINE PointerData get_pointer() const; INLINE TrackerData get_tracker() const; INLINE BatteryData get_battery() const; @@ -244,7 +243,6 @@ PUBLISHED: INLINE bool has_feature(Feature feature) const; // Getters for the various types of device data. - MAKE_PROPERTY2(pointer, has_pointer, get_pointer); MAKE_PROPERTY2(tracker, has_tracker, get_tracker); MAKE_PROPERTY2(battery, has_battery, get_battery); @@ -285,10 +283,10 @@ protected: int add_axis(Axis axis, int minimum, int maximum, bool centered); int add_axis(Axis axis, int minimum, int maximum); - void set_pointer(bool inwin, double x, double y, double time); - void set_pointer_out_of_window(double time); - - void pointer_moved(double x, double y, double time); + int add_pointer(PointerType type, int id, bool primary = false); + void remove_pointer(int id); + void update_pointer(PointerData data, double time); + void pointer_moved(int id, double x, double y, double time); void button_changed(int index, bool down); void axis_changed(int index, int value); void set_axis_value(int index, double state); @@ -323,6 +321,10 @@ protected: PT(ButtonEventList) _button_events; PT(PointerEventList) _pointer_events; + size_t _num_pointers = 0; + typedef pvector Pointers; + Pointers _pointers; + PUBLISHED: typedef pvector Buttons; typedef pvector Axes; diff --git a/panda/src/display/graphicsWindow.cxx b/panda/src/display/graphicsWindow.cxx index 6ff45ed603..ee686528da 100644 --- a/panda/src/display/graphicsWindow.cxx +++ b/panda/src/display/graphicsWindow.cxx @@ -363,7 +363,7 @@ get_pointer(int device) const { { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData()); - result = _input_devices[device]->get_pointer(); + result = ((const GraphicsWindowInputDevice *)_input_devices[device].p())->get_pointer(); } return result; } diff --git a/panda/src/display/graphicsWindowInputDevice.I b/panda/src/display/graphicsWindowInputDevice.I index cba6bfdb30..d313af2c82 100644 --- a/panda/src/display/graphicsWindowInputDevice.I +++ b/panda/src/display/graphicsWindowInputDevice.I @@ -12,23 +12,27 @@ */ /** - * To be called by a particular kind of GraphicsWindow to indicate that the - * pointer is within the window, at the given pixel coordinates. + * Returns the PointerData associated with the input device's pointer. This + * only makes sense if has_pointer() also returns true. */ -INLINE void GraphicsWindowInputDevice:: -set_pointer_in_window(double x, double y, double time) { +INLINE PointerData GraphicsWindowInputDevice:: +get_pointer() const { LightMutexHolder holder(_lock); - InputDevice::set_pointer(true, x, y, time); + if (!_pointers.empty()) { + return _pointers[0]; + } else { + return PointerData(); + } } /** * To be called by a particular kind of GraphicsWindow to indicate that the - * pointer is no longer within the window. + * pointer data has changed. */ INLINE void GraphicsWindowInputDevice:: -set_pointer_out_of_window(double time) { +update_pointer(PointerData data, double time) { LightMutexHolder holder(_lock); - InputDevice::set_pointer_out_of_window(time); + InputDevice::update_pointer(std::move(data), time); } /** @@ -38,5 +42,15 @@ set_pointer_out_of_window(double time) { INLINE void GraphicsWindowInputDevice:: pointer_moved(double x, double y, double time) { LightMutexHolder holder(_lock); - InputDevice::pointer_moved(x, y, time); + InputDevice::pointer_moved(0, x, y, time); +} + +/** + * To be called by a particular kind of GraphicsWindow to indicate that the + * pointer no longer exists. + */ +INLINE void GraphicsWindowInputDevice:: +remove_pointer(int id) { + LightMutexHolder holder(_lock); + InputDevice::remove_pointer(id); } diff --git a/panda/src/display/graphicsWindowInputDevice.cxx b/panda/src/display/graphicsWindowInputDevice.cxx index b747c64b3c..04bac6d81d 100644 --- a/panda/src/display/graphicsWindowInputDevice.cxx +++ b/panda/src/display/graphicsWindowInputDevice.cxx @@ -34,6 +34,7 @@ GraphicsWindowInputDevice(GraphicsWindow *host, const string &name, bool pointer { if (pointer) { enable_feature(Feature::pointer); + add_pointer(PointerType::mouse, 0); } if (keyboard) { enable_feature(Feature::keyboard); @@ -156,3 +157,45 @@ raw_button_up(ButtonHandle button, double time) { LightMutexHolder holder(_lock); _button_events->add_event(ButtonEvent(button, ButtonEvent::T_raw_up, time)); } + +/** + * To be called by a particular kind of GraphicsWindow to indicate that the + * pointer is within the window, at the given pixel coordinates. + */ +void GraphicsWindowInputDevice:: +set_pointer_in_window(double x, double y, double time) { + LightMutexHolder holder(_lock); + PointerData data = _pointers[0]; + data._id = 0; + data._type = PointerType::mouse; + data._xpos = x; + data._ypos = y; + data._in_window = true; + InputDevice::update_pointer(data, time); +} + +/** + * To be called by a particular kind of GraphicsWindow to indicate that the + * pointer is no longer within the window. + */ +void GraphicsWindowInputDevice:: +set_pointer_out_of_window(double time) { + LightMutexHolder holder(_lock); + if (_pointers.empty()) { + return; + } + + _pointers[0]._in_window = false; + _pointers[0]._pressure = 0.0; + + if (_enable_pointer_events) { + int seq = _event_sequence++; + if (_pointer_events.is_null()) { + _pointer_events = new PointerEventList(); + } + _pointer_events->add_event(_pointers[0]._in_window, + _pointers[0]._xpos, + _pointers[0]._ypos, + seq, time); + } +} diff --git a/panda/src/display/graphicsWindowInputDevice.h b/panda/src/display/graphicsWindowInputDevice.h index aa3d97cd7b..22317a1ab0 100644 --- a/panda/src/display/graphicsWindowInputDevice.h +++ b/panda/src/display/graphicsWindowInputDevice.h @@ -53,9 +53,12 @@ PUBLISHED: void raw_button_down(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); void raw_button_up(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time()); - INLINE void set_pointer_in_window(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); - INLINE void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE PointerData get_pointer() const; + void set_pointer_in_window(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); + void set_pointer_out_of_window(double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE void update_pointer(PointerData data, double time = ClockObject::get_global_clock()->get_frame_time()); INLINE void pointer_moved(double x, double y, double time = ClockObject::get_global_clock()->get_frame_time()); + INLINE void remove_pointer(int id); private: typedef pset ButtonsHeld; diff --git a/panda/src/display/mouseAndKeyboard.cxx b/panda/src/display/mouseAndKeyboard.cxx index 97c6447bdb..b1eb49027d 100644 --- a/panda/src/display/mouseAndKeyboard.cxx +++ b/panda/src/display/mouseAndKeyboard.cxx @@ -27,7 +27,7 @@ MouseAndKeyboard:: MouseAndKeyboard(GraphicsWindow *window, int device, const std::string &name) : DataNode(name), _window(window), - _device(device) + _device(window->get_input_device(device)) { _pixel_xy_output = define_output("pixel_xy", EventStoreVec2::get_class_type()); _pixel_size_output = define_output("pixel_size", EventStoreVec2::get_class_type()); @@ -38,6 +38,8 @@ MouseAndKeyboard(GraphicsWindow *window, int device, const std::string &name) : _pixel_xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); _pixel_size = new EventStoreVec2(LPoint2(0.0f, 0.0f)); _xy = new EventStoreVec2(LPoint2(0.0f, 0.0f)); + + _device->enable_pointer_events(); } /** @@ -47,23 +49,9 @@ MouseAndKeyboard(GraphicsWindow *window, int device, const std::string &name) : void MouseAndKeyboard:: set_source(GraphicsWindow *window, int device) { _window = window; - _device = device; -} + _device = window->get_input_device(device); -/** - * Returns the associated source window. - */ -PT(GraphicsWindow) MouseAndKeyboard:: -get_source_window() const { - return _window; -} - -/** - * Returns the associated source device. - */ -int MouseAndKeyboard:: -get_source_device() const { - return _device; + //_device->enable_pointer_events(); } /** @@ -78,7 +66,7 @@ void MouseAndKeyboard:: do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &, DataNodeTransmit &output) { - PT(InputDevice) device = _window->get_input_device(_device); + GraphicsWindowInputDevice *device = (GraphicsWindowInputDevice *)_device.p(); if (device->has_button_event()) { PT(ButtonEventList) bel = device->get_button_events(); diff --git a/panda/src/display/mouseAndKeyboard.h b/panda/src/display/mouseAndKeyboard.h index 9f16b54988..66cad13dc0 100644 --- a/panda/src/display/mouseAndKeyboard.h +++ b/panda/src/display/mouseAndKeyboard.h @@ -43,9 +43,6 @@ PUBLISHED: explicit MouseAndKeyboard(GraphicsWindow *window, int device, const std::string &name); void set_source(GraphicsWindow *window, int device); - PT(GraphicsWindow) get_source_window() const; - int get_source_device() const; - protected: // Inherited from DataNode virtual void do_transmit_data(DataGraphTraverser *trav, @@ -65,7 +62,7 @@ private: PT(EventStoreVec2) _xy; PT(GraphicsWindow) _window; - int _device; + PT(InputDevice) _device; public: static TypeHandle get_class_type() { diff --git a/panda/src/event/pointerEvent.I b/panda/src/event/pointerEvent.I index 835725bd42..003cbf5df0 100644 --- a/panda/src/event/pointerEvent.I +++ b/panda/src/event/pointerEvent.I @@ -11,24 +11,6 @@ * @date 2007-09-20 */ -/** - * - */ -INLINE PointerEvent:: -PointerEvent() : - _in_window(false), - _xpos(0), - _ypos(0), - _dx(0), - _dy(0), - _length(0.0), - _direction(0.0), - _rotation(0.0), - _sequence(0), - _time(0.0) -{ -} - /** * The equality operator does not consider time significant. */ diff --git a/panda/src/event/pointerEvent.h b/panda/src/event/pointerEvent.h index cd024a5e70..c0c354efd6 100644 --- a/panda/src/event/pointerEvent.h +++ b/panda/src/event/pointerEvent.h @@ -15,7 +15,7 @@ #define POINTEREVENT_H #include "pandabase.h" -#include "mouseData.h" +#include "pointerData.h" class Datagram; class DatagramIterator; @@ -25,7 +25,7 @@ class DatagramIterator; */ class EXPCL_PANDA_EVENT PointerEvent { public: - INLINE PointerEvent(); + PointerEvent() = default; INLINE bool operator == (const PointerEvent &other) const; INLINE bool operator != (const PointerEvent &other) const; @@ -37,16 +37,19 @@ public: void read_datagram(DatagramIterator &scan); public: - bool _in_window; - int _xpos; - int _ypos; - double _dx; - double _dy; - double _length; - double _direction; - double _rotation; - int _sequence; - double _time; + bool _in_window = false; + int _id = 0; + PointerType _type = PointerType::unknown; + double _xpos = 0.0; + double _ypos = 0.0; + double _dx = 0.0; + double _dy = 0.0; + double _length = 0.0; + double _direction = 0.0; + double _pressure = 0.0; + double _rotation = 0.0; + int _sequence = 0; + double _time = 0.0; }; INLINE std::ostream &operator << (std::ostream &out, const PointerEvent &pe) { diff --git a/panda/src/event/pointerEventList.I b/panda/src/event/pointerEventList.I index 2d7f3f6d05..8309609832 100644 --- a/panda/src/event/pointerEventList.I +++ b/panda/src/event/pointerEventList.I @@ -43,6 +43,22 @@ get_num_events() const { return _events.size(); } +/** + * Returns true if the list is empty. + */ +INLINE bool PointerEventList:: +empty() const { + return _events.empty(); +} + +/** + * Returns the nth event in the list. + */ +INLINE const PointerEvent &PointerEventList:: +get_event(size_t n) const { + return _events[n]; +} + /** * Get the in-window flag of the nth event. */ diff --git a/panda/src/event/pointerEventList.cxx b/panda/src/event/pointerEventList.cxx index 2109390426..ea160ea9b9 100644 --- a/panda/src/event/pointerEventList.cxx +++ b/panda/src/event/pointerEventList.cxx @@ -76,6 +76,42 @@ write(std::ostream &out, int indent_level) const { } } +/** + * Adds a new event from the given PointerData object. + */ +void PointerEventList:: +add_event(const PointerData &data, int seq, double time) { + PointerEvent pe; + pe._in_window = data._in_window; + pe._type = data._type; + pe._id = data._id; + pe._xpos = data._xpos; + pe._ypos = data._ypos; + pe._pressure = data._pressure; + pe._sequence = seq; + pe._time = time; + if (_events.size() > 0) { + pe._dx = data._xpos - _events.back()._xpos; + pe._dy = data._ypos - _events.back()._ypos; + double ddx = pe._dx; + double ddy = pe._dy; + pe._length = csqrt(ddx*ddx + ddy*ddy); + if (pe._length > 0.0) { + pe._direction = normalize_angle(rad_2_deg(catan2(-ddy,ddx))); + } else { + pe._direction = _events.back()._direction; + } + pe._rotation = delta_angle(_events.back()._direction, pe._direction); + } else { + pe._dx = 0; + pe._dy = 0; + pe._length = 0.0; + pe._direction = 0.0; + pe._rotation = 0.0; + } + _events.push_back(pe); +} + /** * Adds a new event to the end of the list. Automatically calculates the dx, * dy, length, direction, and rotation for all but the first event. diff --git a/panda/src/event/pointerEventList.h b/panda/src/event/pointerEventList.h index c8c3444c9c..e9ae1c9610 100644 --- a/panda/src/event/pointerEventList.h +++ b/panda/src/event/pointerEventList.h @@ -48,6 +48,7 @@ PUBLISHED: INLINE void clear(); INLINE void pop_front(); + void add_event(const PointerData &data, int seq, double time); void add_event(bool in_win, int xpos, int ypos, int seq, double time); void add_event(bool in_win, int xpos, int ypos, double xdelta, double ydelta, int seq, double time); @@ -60,6 +61,9 @@ public: INLINE PointerEventList(const PointerEventList ©); INLINE void operator = (const PointerEventList ©); + INLINE bool empty() const; + INLINE const PointerEvent &get_event(size_t n) const; + virtual void output(std::ostream &out) const; void write(std::ostream &out, int indent_level = 0) const; diff --git a/panda/src/putil/pointerData.I b/panda/src/putil/pointerData.I index a23b1c464b..db508d8e71 100644 --- a/panda/src/putil/pointerData.I +++ b/panda/src/putil/pointerData.I @@ -47,6 +47,14 @@ get_id() const { return _id; } +/** + * Returns the type of pointing device. + */ +INLINE PointerType PointerData:: +get_type() const { + return _type; +} + /** * Returns the pressure of the pointer. For mice, this will be 1.0 if any * button is pressed, 0.0 otherwise. diff --git a/panda/src/putil/pointerData.h b/panda/src/putil/pointerData.h index ab0aee374e..30580cbd6a 100644 --- a/panda/src/putil/pointerData.h +++ b/panda/src/putil/pointerData.h @@ -18,6 +18,19 @@ #include "modifierButtons.h" +BEGIN_PUBLISH +/** + * Contains the types of pointer device. + */ +enum class PointerType { + unknown, + mouse, + finger, + stylus, + eraser, +}; +END_PUBLISH + /** * Holds the data that might be generated by a 2-d pointer input device, such * as the mouse in the GraphicsWindow. @@ -30,6 +43,7 @@ PUBLISHED: public: INLINE int get_id() const; + INLINE PointerType get_type() const; INLINE double get_pressure() const; void output(std::ostream &out) const; @@ -37,6 +51,7 @@ public: PUBLISHED: MAKE_PROPERTY(x, get_x); MAKE_PROPERTY(y, get_y); + MAKE_PROPERTY(type, get_type); MAKE_PROPERTY(id, get_id); MAKE_PROPERTY(in_window, get_in_window); MAKE_PROPERTY(pressure, get_pressure); @@ -46,6 +61,7 @@ public: double _xpos = 0.0; double _ypos = 0.0; double _pressure = 0.0; + PointerType _type = PointerType::unknown; int _id = 0; }; diff --git a/panda/src/tform/mouseWatcher.I b/panda/src/tform/mouseWatcher.I index f47c07e6e2..2b52834551 100644 --- a/panda/src/tform/mouseWatcher.I +++ b/panda/src/tform/mouseWatcher.I @@ -542,7 +542,7 @@ get_trail_log() const { * frame. The trail log is updated once per frame, during the process_events * operation. */ -INLINE int MouseWatcher:: +INLINE size_t MouseWatcher:: num_trail_recent() const { return _num_trail_recent; } diff --git a/panda/src/tform/mouseWatcher.cxx b/panda/src/tform/mouseWatcher.cxx index c4b5c0a6e7..9344d51d7d 100644 --- a/panda/src/tform/mouseWatcher.cxx +++ b/panda/src/tform/mouseWatcher.cxx @@ -1326,7 +1326,7 @@ do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input, const PointerEventList *this_pointer_events; DCAST_INTO_V(this_pointer_events, input.get_data(_pointer_events_input).get_ptr()); _num_trail_recent = this_pointer_events->get_num_events(); - for (int i = 0; i < _num_trail_recent; i++) { + for (size_t i = 0; i < _num_trail_recent; i++) { bool in_win = this_pointer_events->get_in_window(i); int xpos = this_pointer_events->get_xpos(i); int ypos = this_pointer_events->get_ypos(i); diff --git a/panda/src/tform/mouseWatcher.h b/panda/src/tform/mouseWatcher.h index 9f21f3d09b..f81da04ba7 100644 --- a/panda/src/tform/mouseWatcher.h +++ b/panda/src/tform/mouseWatcher.h @@ -138,7 +138,7 @@ PUBLISHED: INLINE const std::string &get_inactivity_timeout_event() const; INLINE CPT(PointerEventList) get_trail_log() const; - INLINE int num_trail_recent() const; + INLINE size_t num_trail_recent() const; void set_trail_log_duration(double duration); PT(GeomNode) get_trail_node(); void clear_trail_node(); @@ -220,7 +220,7 @@ private: LVecBase4 _frame; PT(PointerEventList) _trail_log; - int _num_trail_recent; + size_t _num_trail_recent; double _trail_log_duration; PT(GeomNode) _trail_node; diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 01d952b832..40760ca1d9 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -130,7 +130,7 @@ get_pointer(int device) const { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData()); - result = _input_devices[device]->get_pointer(); + result = ((const GraphicsWindowInputDevice *)_input_devices[device].p())->get_pointer(); // We recheck this immediately to get the most up-to-date value. POINT cpos; diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index b6af0a9219..ef86c82166 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -150,7 +150,7 @@ get_pointer(int device) const { LightMutexHolder holder(_input_lock); nassertr(device >= 0 && device < (int)_input_devices.size(), MouseData()); - result = _input_devices[device]->get_pointer(); + result = ((const GraphicsWindowInputDevice *)_input_devices[device].p())->get_pointer(); // We recheck this immediately to get the most up-to-date value, but we // won't bother waiting for the lock if we can't. From f8520773e94efb4ccd63d334a4ae7f3e1718017f Mon Sep 17 00:00:00 2001 From: Sebastian Hoffmann Date: Fri, 30 Nov 2018 21:02:02 +0100 Subject: [PATCH 303/424] samples: add input device tester program Closes #234 --- samples/gamepad/device_tester.py | 456 +++++++++++++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 samples/gamepad/device_tester.py diff --git a/samples/gamepad/device_tester.py b/samples/gamepad/device_tester.py new file mode 100644 index 0000000000..a3f70b96b4 --- /dev/null +++ b/samples/gamepad/device_tester.py @@ -0,0 +1,456 @@ +#!/usr/bin/env python + +import sys + +from direct.showbase.ShowBase import ShowBase +from direct.showbase.DirectObject import DirectObject +from panda3d.core import InputDeviceManager, InputDevice +from panda3d.core import VBase4, Vec2 +from panda3d.core import TextNode +from direct.gui.DirectGui import ( + DGG, + DirectFrame, + DirectButton, + DirectLabel, + DirectScrolledFrame, + DirectSlider, +) + + +class Main(ShowBase): + def __init__(self): + super().__init__() + base.disableMouse() + self.accept("escape", sys.exit) + self.device_connectivity_monitor = DeviceConnectivityMonitor() + + +class DeviceConnectivityMonitor(DirectObject): + def __init__(self): + super().__init__() + self.mgr = InputDeviceManager.get_global_ptr() + self.create_device_menu() + + self.devices = {} + for device in self.mgr.get_devices(): + self.connect_device(device) + + self.accept("connect-device", self.connect_device) + self.accept("disconnect-device", self.disconnect_device) + + def create_device_menu(self): + self.current_panel = None + self.buttons = {} + self.devices_frame = DirectScrolledFrame( + frameSize=VBase4( + 0, + base.a2dLeft*-0.75, + base.a2dBottom - base.a2dTop, + 0, + ), + frameColor=VBase4(0, 0, 0.25, 1.0), + canvasSize=VBase4( + 0, + base.a2dLeft*-0.75, + 0, + 0, + ), + scrollBarWidth=0.08, + manageScrollBars=True, + autoHideScrollBars=True, + pos=(base.a2dLeft, 0, base.a2dTop), + parent=base.aspect2d, + ) + + self.devices_frame.setCanvasSize() + + def create_menu_button(self, device): + button = DirectButton( + command=self.switch_to_panel, + extraArgs=[device], + text=device.name, + text_scale=0.05, + text_align=TextNode.ALeft, + text_fg=VBase4(0.0, 0.0, 0.0, 1.0), + text_pos=Vec2(0.01, base.a2dBottom / 10.0), + relief=1, + pad=Vec2(0.01, 0.01), + frameColor=VBase4(0.8, 0.8, 0.8, 1.0), + frameSize=VBase4( + 0.0, + base.a2dLeft*-0.75 - 0.081, # 0.08=Scrollbar, 0.001=inaccuracy + base.a2dBottom / 5.0, + 0.0, + ), + parent=self.devices_frame.getCanvas(), + ) + self.buttons[device] = button + + def destroy_menu_button(self, device): + self.buttons[device].detach_node() + del self.buttons[device] + + def refresh_device_menu(self): + self.devices_frame['canvasSize'] = VBase4( + 0, + base.a2dLeft*-0.75, + base.a2dBottom / 5.0 * len(self.buttons), + 0, + ) + self.devices_frame.setCanvasSize() + sorted_buttons = sorted(self.buttons.items(), key=lambda i: i[0].name) + for idx, (dev, button) in enumerate(sorted_buttons): + button.set_pos( + 0, + 0, + (base.a2dBottom / 5.0) * idx, + ) + + def switch_to_panel(self, device): + if self.current_panel is not None: + self.devices[self.current_panel].hide() + self.current_panel = device + self.devices[self.current_panel].show() + + def connect_device(self, device): + self.devices[device] = DeviceMonitor(device) + self.switch_to_panel(device) + self.create_menu_button(device) + self.refresh_device_menu() + + def disconnect_device(self, device): + self.devices[device].deactivate() + del self.devices[device] + if self.current_panel == device: + self.current_panel = None + if len(self.devices) > 0: + active_device = sorted( + self.devices.keys(), + key=lambda d: d.name, + )[0] + self.switch_to_panel(active_device) + + self.destroy_menu_button(device) + self.refresh_device_menu() + + +class DeviceMonitor(DirectObject): + def __init__(self, device): + super().__init__() + self.device = device + self.create_panel() + self.activate() + self.hide() + + def activate(self): + print("Device connected") + print(" Name : {}".format(self.device.name)) + print(" Type : {}".format(self.device.device_class.name)) + print(" Manufacturer: {}".format(self.device.manufacturer)) + print(" ID : {:04x}:{:04x}".format(self.device.vendor_id, + self.device.product_id)) + axis_names = [axis.axis.name for axis in self.device.axes] + print(" Axes : {} ({})".format(len(self.device.axes), + ', '.join(axis_names))) + button_names = [button.handle.name for button in self.device.buttons] + print(" Buttons : {} ({})".format(len(self.device.buttons), + ', '.join(button_names))) + + base.attachInputDevice(self.device) + + self.task = base.taskMgr.add( + self.update, + "Monitor for {}".format(self.device.name), + sort=10, + ) + + def deactivate(self): + print("\"{}\" disconnected".format(self.device.name)) + base.taskMgr.remove(self.task) + self.panel.detach_node() + + def create_panel(self): + panel_width = base.a2dLeft * -0.25 + base.a2dRight + scroll_bar_width = 0.08 + # NOTE: -0.001 because thanks to inaccuracy the vertical bar appears... + canvas_width = panel_width - scroll_bar_width - 0.001 + canvas_height = base.a2dBottom - base.a2dTop + + self.panel = DirectScrolledFrame( + frameSize=VBase4( + 0, + panel_width, + canvas_height, + 0, + ), + frameColor=VBase4(0.8, 0.8, 0.8, 1), + canvasSize=VBase4( + 0, + canvas_width, + canvas_height, + 0, + ), + scrollBarWidth=scroll_bar_width, + manageScrollBars=True, + autoHideScrollBars=True, + pos=(base.a2dLeft * 0.25, 0, base.a2dTop), + parent=base.aspect2d, + ) + panel_canvas = self.panel.getCanvas() + offset = -0.0 + + # Style sheets + + half_width_entry = dict( + frameSize=VBase4( + 0, + canvas_width / 2, + -0.1, + 0, + ), + parent=panel_canvas, + frameColor=VBase4(0.8, 0.8, 0.8, 1), + ) + left_aligned_small_text = dict( + text_align=TextNode.ALeft, + text_scale=0.05, + text_fg=VBase4(0,0,0,1), + text_pos=(0.05, -0.06), + ) + half_width_text_frame = dict( + **half_width_entry, + **left_aligned_small_text, + ) + + header = dict( + frameSize=VBase4( + 0, + canvas_width, + -0.1, + 0, + ), + parent=panel_canvas, + frameColor=VBase4(0.6, 0.6, 0.6, 1), + text_align=TextNode.ALeft, + text_scale=0.1, + text_fg=VBase4(0,0,0,1), + text_pos=(0.05, -0.075), + ) + + # Basic device data (name, device class, manufacturer, USB ID) + + self.device_header = DirectLabel( + text="Device data", + pos=(0, 0, offset), + **header, + ) + offset -= 0.1 + + def add_data_entry(offset, label, text): + self.name = DirectLabel( + text=label, + pos=(0, 0, offset), + **half_width_text_frame, + ) + self.name = DirectLabel( + text=text, + pos=(canvas_width / 2, 0, offset), + **half_width_text_frame, + ) + + metadata = [ + ('Name', self.device.name), + ('Device class', self.device.device_class.name), + ('Manufacturer', self.device.manufacturer), + ('USB ID', + "{:04x}:{:04x}".format( + self.device.vendor_id, + self.device.product_id, + ), + ), + ] + for label, text in metadata: + add_data_entry(offset, label, text) + offset -= 0.1 + + # Axes + + self.axis_sliders = [] + if len(self.device.axes) > 0: + offset -= 0.1 + self.axes_header = DirectLabel( + text="Axes", + pos=(0, 0, offset), + **header, + ) + offset -= 0.1 + + def add_axis(offset, axis_name): + slider_width = canvas_width / 2 + label = DirectLabel( + text=axis_name, + **left_aligned_small_text, + pos=(0.05, 0, offset), + parent=panel_canvas, + ) + slider = DirectSlider( + value=0.0, + range=(-1.0, 1.0), + state=DGG.DISABLED, + frameSize=VBase4( + 0, + slider_width, + -0.1, + 0, + ), + thumb_frameSize=VBase4( + 0.0, + 0.04, + -0.04, + 0.04), + frameColor=VBase4(0.3, 0.3, 0.3, 1), + pos=(canvas_width - slider_width, 0, offset), + parent=panel_canvas, + ) + return slider + + for axis in self.device.axes: + axis_slider = add_axis(offset, axis.axis.name) + self.axis_sliders.append(axis_slider) + offset -= 0.1 + + # Buttons + + self.button_buttons = [] + if len(self.device.buttons) > 0: + offset -= 0.1 + self.buttons_header = DirectLabel( + text="Buttons", + pos=(0, 0, offset), + **header, + ) + offset -= 0.1 + + def add_button(offset, button_name): + button_width = canvas_width / 2 + label = DirectLabel( + text=button_name, + **left_aligned_small_text, + pos=(0.05, 0, offset), + parent=panel_canvas, + ) + button = DirectFrame( + frameSize=VBase4( + 0, + button_width, + -0.1, + 0, + ), + text="", + text_align=TextNode.ACenter, + text_scale=0.05, + text_fg=VBase4(0,0,0,1), + text_pos=(button_width / 2, -0.06), + frameColor=VBase4(0.3, 0.3, 0.3, 1), + pos=(canvas_width - button_width, 0, offset), + parent=panel_canvas, + ) + return button + + for i in range(len(self.device.buttons)): + button_name = self.device.buttons[i].handle.name + button_button = add_button(offset, button_name) + self.button_buttons.append(button_button) + offset -= 0.1 + + # Vibration + + self.vibration = [] + if self.device.has_feature(InputDevice.Feature.vibration): + offset -= 0.1 + self.vibration_header = DirectLabel( + text="Vibration", + pos=(0, 0, offset), + **header, + ) + offset -= 0.1 + + def add_vibration(offset, axis_name, index): + slider_width = canvas_width / 2 + label = DirectLabel( + text=axis_name, + **left_aligned_small_text, + pos=(0.05, 0, offset), + parent=panel_canvas, + ) + slider = DirectSlider( + value=0.0, + range=(0.0, 1.0), + command=self.update_vibration, + frameSize=VBase4( + 0, + slider_width, + -0.1, + 0, + ), + thumb_frameSize=VBase4( + 0.0, + 0.04, + -0.04, + 0.04), + frameColor=VBase4(0.3, 0.3, 0.3, 1), + pos=(canvas_width - slider_width, 0, offset), + parent=panel_canvas, + ) + return slider + + for index, name in enumerate(["low frequency", "high frequency"]): + self.vibration.append(add_vibration(offset, name, index)) + offset -= 0.1 + + # Resize the panel's canvas to the widgets actually in it. + if -offset > -canvas_height: + self.panel['canvasSize'] = VBase4( + 0, + canvas_width, + offset, + 0, + ) + self.panel.setCanvasSize() + + def show(self): + # FIXME: Activate update task here, and deactivate it in hide()? + self.panel.show() + + def hide(self): + self.panel.hide() + + def update_vibration(self): + low = self.vibration[0]['value'] + high = self.vibration[1]['value'] + self.device.set_vibration(low, high) + + def update(self, task): + # FIXME: There needs to be a demo of events here, too. + for idx, slider in enumerate(self.axis_sliders): + slider["value"] = self.device.axes[idx].value + for idx, button in enumerate(self.button_buttons): + if self.device.buttons[idx].known: + if self.device.buttons[idx].pressed: + button['frameColor'] = VBase4(0.0, 0.8, 0.0, 1) + button['text'] = "down" + else: + button['frameColor'] = VBase4(0.3, 0.3, 0.3, 1) + button['text'] = "up" + else: + # State is InputDevice.S_unknown. This happens if the device + # manager hasn't polled yet, and in some cases before a button + # has been pressed after the program's start. + button['frameColor'] = VBase4(0.8, 0.8, 0.0, 1) + button['text'] = "unknown" + return task.cont + + +if __name__ == '__main__': + main = Main() + main.run() From ad8a6b4a885f5cf1106c2eb2a1ea2d18a8f0e9f3 Mon Sep 17 00:00:00 2001 From: Younguk Kim Date: Sun, 2 Dec 2018 14:44:40 +0900 Subject: [PATCH 304/424] display: fix pointer casting in get_pointer() --- panda/src/windisplay/winGraphicsWindow.cxx | 2 +- panda/src/x11display/x11GraphicsWindow.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/windisplay/winGraphicsWindow.cxx b/panda/src/windisplay/winGraphicsWindow.cxx index 40760ca1d9..195189ac9e 100644 --- a/panda/src/windisplay/winGraphicsWindow.cxx +++ b/panda/src/windisplay/winGraphicsWindow.cxx @@ -138,7 +138,7 @@ get_pointer(int device) const { double time = ClockObject::get_global_clock()->get_real_time(); result._xpos = cpos.x; result._ypos = cpos.y; - ((GraphicsWindowInputDevice &)_input_devices[0]).set_pointer_in_window(result._xpos, result._ypos, time); + ((GraphicsWindowInputDevice *)_input_devices[0].p())->set_pointer_in_window(result._xpos, result._ypos, time); } } return result; diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index ef86c82166..caaacc4a9c 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -164,7 +164,7 @@ get_pointer(int device) const { double time = ClockObject::get_global_clock()->get_real_time(); result._xpos = event.xbutton.x; result._ypos = event.xbutton.y; - ((GraphicsWindowInputDevice &)_input_devices[0]).set_pointer_in_window(result._xpos, result._ypos, time); + ((GraphicsWindowInputDevice *)_input_devices[0].p())->set_pointer_in_window(result._xpos, result._ypos, time); } x11GraphicsPipe::_x_mutex.release(); } From cf277ebf6c706215b127de4b2bf191bc4f74a0e0 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 2 Dec 2018 12:13:58 +0100 Subject: [PATCH 305/424] maya2egg: fix a potential crash (see #434) --- pandatool/src/mayaegg/mayaEggLoader.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandatool/src/mayaegg/mayaEggLoader.cxx b/pandatool/src/mayaegg/mayaEggLoader.cxx index 68fcb8d0f7..a60e5d2372 100644 --- a/pandatool/src/mayaegg/mayaEggLoader.cxx +++ b/pandatool/src/mayaegg/mayaEggLoader.cxx @@ -975,7 +975,9 @@ MayaEggMesh *MayaEggLoader::GetMesh(EggVertexPool *pool, EggGroup *parent) MayaEggMesh *result = _mesh_tab[parent]; if (result == 0) { result = new MayaEggMesh; - result->_name = parent->get_name(); + if (parent != nullptr) { + result->_name = parent->get_name(); + } result->_pool = pool; result->_parent = parent; result->_vert_count = 0; From 43ce32478fb17db29a5bc861fae02380944968f5 Mon Sep 17 00:00:00 2001 From: kamgha Date: Sun, 2 Dec 2018 12:17:04 +0100 Subject: [PATCH 306/424] makepanda: find OpenEXR 2.3 and its static variant for MSVC Closes #463 --- makepanda/makepanda.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 608648d5a1..43c42e5fd9 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -647,10 +647,17 @@ if (COMPILER == "MSVC"): suffix = "" if os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_2.lib"): suffix = "-2_2" + elif os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_3.lib"): + suffix = "-2_3" + if os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf" + suffix + "_s.lib"): + suffix += "_s" LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmImf" + suffix + ".lib") LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmThread" + suffix + ".lib") LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Iex" + suffix + ".lib") - LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half.lib") + if suffix == "-2_2": + LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half.lib") + else: + LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half" + suffix + ".lib") IncDirectory("OPENEXR", GetThirdpartyDir() + "openexr/include/OpenEXR") if (PkgSkip("JPEG")==0): LibName("JPEG", GetThirdpartyDir() + "jpeg/lib/jpeg-static.lib") if (PkgSkip("ZLIB")==0): LibName("ZLIB", GetThirdpartyDir() + "zlib/lib/zlibstatic.lib") @@ -681,8 +688,7 @@ if (COMPILER == "MSVC"): IncDirectory("FCOLLADA", GetThirdpartyDir() + "fcollada/include/FCollada") if (PkgSkip("ASSIMP")==0): LibName("ASSIMP", GetThirdpartyDir() + "assimp/lib/assimp.lib") - path = GetThirdpartyDir() + "assimp/lib/IrrXML.lib" - if os.path.isfile(path): + if os.path.isfile(GetThirdpartyDir() + "assimp/lib/IrrXML.lib"): LibName("ASSIMP", GetThirdpartyDir() + "assimp/lib/IrrXML.lib") IncDirectory("ASSIMP", GetThirdpartyDir() + "assimp/include") if (PkgSkip("SQUISH")==0): From 192913bf8ec5df92f3e007db73046d06e655973e Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 2 Dec 2018 16:26:22 +0100 Subject: [PATCH 307/424] freezer: work around Python modulefinder bug (bpo-35376) --- direct/src/showutil/FreezeTool.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 45ae698413..58e7e99ad8 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -2306,6 +2306,31 @@ class PandaModuleFinder(modulefinder.ModuleFinder): self.msgout(2, "load_module ->", m) return m + # This function is provided here since the Python library version has a bug + # (see bpo-35376) + def _safe_import_hook(self, name, caller, fromlist, level=-1): + # wrapper for self.import_hook() that won't raise ImportError + if name in self.badmodules: + self._add_badmodule(name, caller) + return + try: + self.import_hook(name, caller, level=level) + except ImportError as msg: + self.msg(2, "ImportError:", str(msg)) + self._add_badmodule(name, caller) + else: + if fromlist: + for sub in fromlist: + fullname = name + "." + sub + if fullname in self.badmodules: + self._add_badmodule(fullname, caller) + continue + try: + self.import_hook(name, caller, [sub], level=level) + except ImportError as msg: + self.msg(2, "ImportError:", str(msg)) + self._add_badmodule(fullname, caller) + def find_module(self, name, path=None, parent=None): """ Finds a module with the indicated name on the given search path (or self.path if None). Returns a tuple like (fp, path, stuff), where From 3124564040b186c3ed40ddb4cc7f756ec092690a Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 2 Dec 2018 16:27:08 +0100 Subject: [PATCH 308/424] deploy-ng: fix crash when using local logfile in non-writable dir Fixes #462 --- pandatool/src/deploy-stub/deploy-stub.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 0acf31085a..5ce429335f 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -141,8 +141,8 @@ static int mkdir_parent(const wchar_t *path) { break; } } - if (buflen == 0) { - // There was no path separator. + if (buflen == (size_t)-1 || buflen == 0) { + // There was no path separator, or this was the root directory. return 0; } @@ -187,8 +187,8 @@ static int mkdir_parent(const char *path) { break; } } - if (buflen == 0) { - // There was no path separator. + if (buflen == (size_t)-1 || buflen == 0) { + // There was no path separator, or this was the root directory. return 0; } if (mkdir(buffer, 0755) == 0) { From 2664a24e6a48f49ac81044e3a907a780c65ddaea Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 2 Dec 2018 20:22:07 +0100 Subject: [PATCH 309/424] deploy-ng: fix redirecting GUI app output to log in VC2015/UCRT Fixes #461 --- pandatool/src/deploy-stub/deploy-stub.c | 26 ++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index 5ce429335f..12079da46d 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -264,17 +264,29 @@ static int setup_logging(const char *path, int append) { SetFilePointer(handle, 0, NULL, FILE_END); } - fflush(stdout); - fflush(stderr); - - int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | (append ? _O_APPEND : 0)); SetStdHandle(STD_OUTPUT_HANDLE, handle); - _dup2(fd, 1); - SetStdHandle(STD_ERROR_HANDLE, handle); - _dup2(fd, 2); + // If we are running under the UCRT in a GUI application, we can't be sure + // that we have valid fds for stdout and stderr, so we have to set them up. + // One way to do this is to reopen them to something silly (like NUL). + if (_fileno(stdout) < 0) { + _close(1); + _wfreopen(L"\\\\.\\NUL", L"w", stdout); + } + + if (_fileno(stderr) < 0) { + _close(2); + _wfreopen(L"\\\\.\\NUL", L"w", stderr); + } + + // Now replace the stdout and stderr file descriptors with one pointing to + // our desired handle. + int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | (append ? _O_APPEND : 0)); + _dup2(fd, _fileno(stdout)); + _dup2(fd, _fileno(stderr)); _close(fd); + return 1; #else // Does it start with a tilde? Perform tilde expansion if so. From 97946cf95ea017150d63f4d9a26e2fd63962e769 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 2 Dec 2018 21:39:41 +0100 Subject: [PATCH 310/424] makepanda: deploy-stub should be a Python-version-specific target --- makepanda/makepanda.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index b043f38a77..a0270a389f 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6647,31 +6647,31 @@ if not PkgSkip("CONTRIB") and not PkgSkip("PYTHON") and not RUNTIME: # DIRECTORY: pandatool/src/deploy-stub # if PkgSkip("PYTHON") == 0: - OPTS=['DIR:pandatool/src/deploy-stub', 'BUILDING:DEPLOYSTUB', 'PYTHON'] - TargetAdd('deploy-stub.obj', opts=OPTS, input='deploy-stub.c') + OPTS=['DIR:pandatool/src/deploy-stub', 'BUILDING:DEPLOYSTUB'] + PyTargetAdd('deploy-stub.obj', opts=OPTS, input='deploy-stub.c') if GetTarget() == 'windows': - TargetAdd('frozen_dllmain.obj', opts=OPTS, input='frozen_dllmain.c') + PyTargetAdd('frozen_dllmain.obj', opts=OPTS, input='frozen_dllmain.c') if GetTarget() == 'linux' or GetTarget() == 'freebsd': # Setup rpath so libs can be found in the same directory as the deployed game LibName('DEPLOYSTUB', "-Wl,-rpath,\$ORIGIN") LibName('DEPLOYSTUB', "-Wl,-z,origin") LibName('DEPLOYSTUB', "-rdynamic") - TargetAdd('deploy-stub.exe', input='deploy-stub.obj') + PyTargetAdd('deploy-stub.exe', input='deploy-stub.obj') if GetTarget() == 'windows': - TargetAdd('deploy-stub.exe', input='frozen_dllmain.obj') - TargetAdd('deploy-stub.exe', opts=['WINSHELL', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) + PyTargetAdd('deploy-stub.exe', input='frozen_dllmain.obj') + PyTargetAdd('deploy-stub.exe', opts=['WINSHELL', 'DEPLOYSTUB', 'NOICON']) if GetTarget() == 'windows': - TargetAdd('deploy-stubw.exe', input='deploy-stub.obj') - TargetAdd('deploy-stubw.exe', input='frozen_dllmain.obj') - TargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'WINSHELL', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) + PyTargetAdd('deploy-stubw.exe', input='deploy-stub.obj') + PyTargetAdd('deploy-stubw.exe', input='frozen_dllmain.obj') + PyTargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'WINSHELL', 'DEPLOYSTUB', 'NOICON']) elif GetTarget() == 'darwin': DefSymbol('MACOS_APP_BUNDLE', 'MACOS_APP_BUNDLE') OPTS = OPTS + ['MACOS_APP_BUNDLE'] - TargetAdd('deploy-stubw.obj', opts=OPTS, input='deploy-stub.c') - TargetAdd('deploy-stubw.exe', input='deploy-stubw.obj') - TargetAdd('deploy-stubw.exe', opts=['MACOS_APP_BUNDLE', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) + PyTargetAdd('deploy-stubw.obj', opts=OPTS, input='deploy-stub.c') + PyTargetAdd('deploy-stubw.exe', input='deploy-stubw.obj') + PyTargetAdd('deploy-stubw.exe', opts=['MACOS_APP_BUNDLE', 'DEPLOYSTUB', 'NOICON']) # # Generate the models directory and samples directory From 8a0f43815675ecfe7cfb5d5f41297abf71a1f60e Mon Sep 17 00:00:00 2001 From: Donny Lawrence Date: Mon, 3 Dec 2018 23:05:05 -0500 Subject: [PATCH 311/424] Get rid of the flickering that occurs on Mojave as a result of changes in the way AppKit handles view layers. --- panda/src/cocoadisplay/cocoaPandaView.mm | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/panda/src/cocoadisplay/cocoaPandaView.mm b/panda/src/cocoadisplay/cocoaPandaView.mm index c3471ec36b..6a3f48b39a 100644 --- a/panda/src/cocoadisplay/cocoaPandaView.mm +++ b/panda/src/cocoadisplay/cocoaPandaView.mm @@ -26,6 +26,10 @@ [self setCanDrawConcurrently:YES]; #endif + // If a layer ends up becoming attached to the view, tell AppKit we'll manage + // the redrawing since we're doing things our own way. + self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawNever; + cocoadisplay_cat.debug() << "Created CocoaPandaView " << self << " for GraphicsWindow " << window << "\n"; _graphicsWindow = window; @@ -167,4 +171,15 @@ - (BOOL) isOpaque { return YES; } + +-(void)setLayer:(CALayer*)layer +{ + [super setLayer:layer]; + + // Starting in macOS 10.14, a CALayer will still be attached to a view even + // if `wantsLayer` is false. If we don't update the context now, only a + // black screen will be rendered until the context is updated some other + // way (like through a window resize event). + [_context update]; +} @end From 301957c59188ae6d83276e4796a1f421f95b9daa Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 4 Dec 2018 12:36:03 +0100 Subject: [PATCH 312/424] gobj: improve performance of GeomPrimitive::make_nonindexed() It now no longer has to create two GeomVertexArrayDataHandle objects for every vertex it visits. --- panda/src/gobj/geomPrimitive.cxx | 27 ++++++++---- panda/src/gobj/geomVertexData.cxx | 68 ++++++++++++++++++++----------- panda/src/gobj/geomVertexData.h | 3 ++ 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/panda/src/gobj/geomPrimitive.cxx b/panda/src/gobj/geomPrimitive.cxx index 011d66b132..2cd5e2739a 100644 --- a/panda/src/gobj/geomPrimitive.cxx +++ b/panda/src/gobj/geomPrimitive.cxx @@ -538,15 +538,26 @@ offset_vertices(int offset, int begin_row, int end_row) { void GeomPrimitive:: make_nonindexed(GeomVertexData *dest, const GeomVertexData *source) { Thread *current_thread = Thread::get_current_thread(); - int num_vertices = get_num_vertices(); - int dest_start = dest->get_num_rows(); - int strip_cut_index = get_strip_cut_index(); - dest->set_num_rows(dest_start + num_vertices); - for (int i = 0; i < num_vertices; ++i) { - int v = get_vertex(i); - nassertd(v != strip_cut_index) continue; - dest->copy_row_from(dest_start + i, source, v, current_thread); + int num_vertices, dest_start; + { + GeomPrimitivePipelineReader reader(this, current_thread); + num_vertices = reader.get_num_vertices(); + int strip_cut_index = reader.get_strip_cut_index(); + + GeomVertexDataPipelineWriter data_writer(dest, false, current_thread); + data_writer.check_array_writers(); + dest_start = data_writer.get_num_rows(); + data_writer.set_num_rows(dest_start + num_vertices); + + GeomVertexDataPipelineReader data_reader(source, current_thread); + data_reader.check_array_readers(); + + for (int i = 0; i < num_vertices; ++i) { + int v = reader.get_vertex(i); + nassertd(v != strip_cut_index) continue; + data_writer.copy_row_from(dest_start + i, data_reader, v); + } } set_nonindexed_vertices(dest_start, num_vertices); diff --git a/panda/src/gobj/geomVertexData.cxx b/panda/src/gobj/geomVertexData.cxx index 45522427c2..d7bf35ba9d 100644 --- a/panda/src/gobj/geomVertexData.cxx +++ b/panda/src/gobj/geomVertexData.cxx @@ -686,32 +686,13 @@ copy_from(const GeomVertexData *source, bool keep_data_objects, void GeomVertexData:: copy_row_from(int dest_row, const GeomVertexData *source, int source_row, Thread *current_thread) { - const GeomVertexFormat *source_format = source->get_format(); - const GeomVertexFormat *dest_format = get_format(); - nassertv(source_format == dest_format); - nassertv(source_row >= 0 && source_row < source->get_num_rows()); - if (dest_row >= get_num_rows()) { - // Implicitly add enough rows to get to the indicated row. - set_num_rows(dest_row + 1); - } + GeomVertexDataPipelineReader reader(source, current_thread); + reader.check_array_readers(); - int num_arrays = source_format->get_num_arrays(); - - for (int i = 0; i < num_arrays; ++i) { - PT(GeomVertexArrayDataHandle) dest_handle = modify_array_handle(i); - unsigned char *dest_array_data = dest_handle->get_write_pointer(); - - CPT(GeomVertexArrayDataHandle) source_array_handle = source->get_array_handle(i); - const unsigned char *source_array_data = source_array_handle->get_read_pointer(true); - - const GeomVertexArrayFormat *array_format = source_format->get_array(i); - int stride = array_format->get_stride(); - - memcpy(dest_array_data + stride * dest_row, - source_array_data + stride * source_row, - stride); - } + GeomVertexDataPipelineWriter writer(this, true, current_thread); + writer.check_array_writers(); + writer.copy_row_from(dest_row, reader, source_row); } /** @@ -2601,6 +2582,45 @@ set_array(size_t i, const GeomVertexArrayData *array) { } } +/** + * Copies a single row of the data from the other array into the indicated row + * of this array. In this case, the source format must exactly match the + * destination format. + * + * Don't call this in a downstream thread unless you don't mind it blowing + * away other changes you might have recently made in an upstream thread. + */ +void GeomVertexDataPipelineWriter:: +copy_row_from(int dest_row, const GeomVertexDataPipelineReader &source, + int source_row) { + const GeomVertexFormat *source_format = source.get_format(); + const GeomVertexFormat *dest_format = get_format(); + nassertv(source_format == dest_format); + nassertv(source_row >= 0 && source_row < source.get_num_rows()); + nassertv(_got_array_writers); + + if (dest_row >= get_num_rows()) { + // Implicitly add enough rows to get to the indicated row. + set_num_rows(dest_row + 1); + } + + size_t num_arrays = source_format->get_num_arrays(); + for (size_t i = 0; i < num_arrays; ++i) { + GeomVertexArrayDataHandle *dest_handle = get_array_writer(i); + unsigned char *dest_array_data = dest_handle->get_write_pointer(); + + const GeomVertexArrayDataHandle *source_array_handle = source.get_array_reader(i); + const unsigned char *source_array_data = source_array_handle->get_read_pointer(true); + + const GeomVertexArrayFormat *array_format = source_format->get_array(i); + int stride = array_format->get_stride(); + + memcpy(dest_array_data + stride * dest_row, + source_array_data + stride * source_row, + stride); + } +} + /** * */ diff --git a/panda/src/gobj/geomVertexData.h b/panda/src/gobj/geomVertexData.h index 9b861c1cbc..2b267045c5 100644 --- a/panda/src/gobj/geomVertexData.h +++ b/panda/src/gobj/geomVertexData.h @@ -526,6 +526,9 @@ public: bool unclean_set_num_rows(int n); bool reserve_num_rows(int n); + void copy_row_from(int dest_row, const GeomVertexDataPipelineReader &source, + int source_row); + private: void make_array_writers(); void delete_array_writers(); From 3c6b6e47ec91195c23e3cf60ab8dcdffd468439d Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 4 Dec 2018 12:43:55 +0100 Subject: [PATCH 313/424] glgsg: don't call glLineWidth with higher value than maximum Officially, in core profile OpenGL 3.2+, calling it with a value higher than 1.0 is an error, but many vendors seem to let us. Apple's implementation does not. It appears that checking the maximum line width is a way to deal with this. Fixes #466 --- .../glstuff/glGraphicsStateGuardian_src.cxx | 24 ++++++++++++++++++- .../src/glstuff/glGraphicsStateGuardian_src.h | 2 ++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 14fec5d146..62d74ce5bc 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -771,6 +771,28 @@ reset() { _supported_geom_rendering |= Geom::GR_point_sprite; } + // Determine whether we support wide lines (and how wide they can be). + { + GLfloat aliased_range[2] = {1.0f, 1.0f}; + glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, aliased_range); + + _max_line_width = aliased_range[1]; + +#ifdef SUPPORT_FIXED_FUNCTION + if (has_fixed_function_pipeline()) { +#ifndef OPENGLES + GLfloat range[2] = {1.0f, 1.0f}; + glGetFloatv(GL_LINE_WIDTH_RANGE, range); + _max_line_width = std::max(_max_line_width, range[1]); +#endif + + GLfloat smooth_range[2] = {1.0f, 1.0f}; + glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, smooth_range); + _max_line_width = std::max(_max_line_width, smooth_range[1]); + } +#endif + } + #ifdef OPENGLES_1 // OpenGL ES 1.0 does not support primitive restart indices. @@ -7352,7 +7374,7 @@ do_issue_render_mode() { GLCAT.spam() << "setting thickness to " << thickness << "\n"; } - glLineWidth(thickness); + glLineWidth(std::min(thickness, _max_line_width)); #ifndef OPENGLES_2 glPointSize(thickness); #endif diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.h b/panda/src/glstuff/glGraphicsStateGuardian_src.h index 4329938d06..3fd89c8ad1 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.h +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.h @@ -685,6 +685,8 @@ protected: #endif #endif + GLfloat _max_line_width; + #ifdef HAVE_CG CGcontext _cg_context; #endif From fb8a2f2af0b3b695885197616c514eef15ecbec8 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 4 Dec 2018 12:51:19 +0100 Subject: [PATCH 314/424] Revert "openal: Grab volume from manager instead of assuming max volume." This reverts commit 7fb30ae070b1b8a65c5996334153ded33c2ce1f9. See #64 for discussion. --- panda/src/audiotraits/openalAudioSound.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/audiotraits/openalAudioSound.cxx b/panda/src/audiotraits/openalAudioSound.cxx index 8d41a61fa3..9244b7509f 100644 --- a/panda/src/audiotraits/openalAudioSound.cxx +++ b/panda/src/audiotraits/openalAudioSound.cxx @@ -44,7 +44,7 @@ OpenALAudioSound(OpenALAudioManager* manager, _loops_completed(0), _source(0), _manager(manager), - _volume(manager->get_volume()), + _volume(1.0f), _balance(0), _play_rate(1.0), _positional(positional), From 3adc7d977edec50c116f20a82dd66608017a99f5 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 4 Dec 2018 16:16:05 +0100 Subject: [PATCH 315/424] glgsg: fix compilation in double-precision mode --- panda/src/glstuff/glGraphicsStateGuardian_src.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 62d74ce5bc..7974a9345c 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -7374,7 +7374,7 @@ do_issue_render_mode() { GLCAT.spam() << "setting thickness to " << thickness << "\n"; } - glLineWidth(std::min(thickness, _max_line_width)); + glLineWidth(std::min((GLfloat)thickness, _max_line_width)); #ifndef OPENGLES_2 glPointSize(thickness); #endif From b38faadf5f835c11d5697ee46b382207edf1d142 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 6 Dec 2018 22:29:09 +0100 Subject: [PATCH 316/424] bullet: restore behavior of in_contact flag c18abad8c3373aff134dd02d099d476d094b0b69 added a call that resets the m_isInContact flag, so this resets it, for better or worse. We may want to revisit whether #349 was indeed the right fix to #250. Fixes #453 --- panda/src/bullet/bulletVehicle.cxx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/panda/src/bullet/bulletVehicle.cxx b/panda/src/bullet/bulletVehicle.cxx index f4321cd911..ba7ee35d6d 100644 --- a/panda/src/bullet/bulletVehicle.cxx +++ b/panda/src/bullet/bulletVehicle.cxx @@ -235,10 +235,13 @@ void BulletVehicle:: do_sync_b2p() { for (int i=0; i < _vehicle->getNumWheels(); i++) { - // synchronize the wheels with the (interpolated) chassis worldtransform - _vehicle->updateWheelTransform(i, true); + btWheelInfo &info = _vehicle->getWheelInfo(i); - btWheelInfo info = _vehicle->getWheelInfo(i); + // synchronize the wheels with the (interpolated) chassis worldtransform. + // It resets the m_isInContact flag, so restore that afterwards. + bool in_contact = info.m_raycastInfo.m_isInContact; + _vehicle->updateWheelTransform(i, true); + info.m_raycastInfo.m_isInContact = in_contact; PandaNode *node = (PandaNode *)info.m_clientInfo; if (node) { From 89236ac1360e5f0249d425e61d537deef40e71c3 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 6 Dec 2018 23:24:45 +0100 Subject: [PATCH 317/424] pgraph: fixed-depth billboards, bam additions (6.43) This makes it possible to use BillboardEffect to specify a fixed distance to the camera, in order to keep them at a fixed apparent size. This also enables serialization to .bam of the reference NodePaths of BillboardEffect and CompassEffect. --- panda/src/pgraph/billboardEffect.I | 9 ++++++ panda/src/pgraph/billboardEffect.cxx | 45 ++++++++++++++++++++++++---- panda/src/pgraph/billboardEffect.h | 9 ++++-- panda/src/pgraph/compassEffect.cxx | 25 ++++++++++++++-- panda/src/pgraph/compassEffect.h | 2 ++ panda/src/pgraph/nodePath.I | 2 +- panda/src/pgraph/nodePath.cxx | 4 +-- panda/src/pgraph/nodePath.h | 4 +-- panda/src/putil/bam.h | 3 +- 9 files changed, 88 insertions(+), 15 deletions(-) diff --git a/panda/src/pgraph/billboardEffect.I b/panda/src/pgraph/billboardEffect.I index d94d8135f7..b6ee6f31b0 100644 --- a/panda/src/pgraph/billboardEffect.I +++ b/panda/src/pgraph/billboardEffect.I @@ -86,6 +86,15 @@ get_axial_rotate() const { return _axial_rotate; } +/** + * Returns true if this billboard always appears at a fixed distance from the + * camera. + */ +INLINE bool BillboardEffect:: +get_fixed_depth() const { + return _fixed_depth; +} + /** * Returns the distance toward the camera (or the look_at_point) the billboard * is moved towards, after rotating. This can be used to ensure the billboard diff --git a/panda/src/pgraph/billboardEffect.cxx b/panda/src/pgraph/billboardEffect.cxx index c5843bb667..60c6e72c97 100644 --- a/panda/src/pgraph/billboardEffect.cxx +++ b/panda/src/pgraph/billboardEffect.cxx @@ -29,7 +29,7 @@ TypeHandle BillboardEffect::_type_handle; CPT(RenderEffect) BillboardEffect:: make(const LVector3 &up_vector, bool eye_relative, bool axial_rotate, PN_stdfloat offset, const NodePath &look_at, - const LPoint3 &look_at_point) { + const LPoint3 &look_at_point, bool fixed_depth) { BillboardEffect *effect = new BillboardEffect; effect->_up_vector = up_vector; effect->_eye_relative = eye_relative; @@ -37,6 +37,7 @@ make(const LVector3 &up_vector, bool eye_relative, effect->_offset = offset; effect->_look_at = look_at; effect->_look_at_point = look_at_point; + effect->_fixed_depth = fixed_depth; effect->_off = false; return return_new(effect); } @@ -82,7 +83,9 @@ output(std::ostream &out) const { if (_eye_relative) { out << " eye"; } - if (_offset != 0.0f) { + if (_fixed_depth) { + out << " depth " << -_offset; + } else if (_offset != 0.0f) { out << " offset " << _offset; } if (!_look_at.is_empty()) { @@ -201,6 +204,9 @@ compare_to_impl(const RenderEffect *other) const { if (_eye_relative != ta->_eye_relative) { return (int)_eye_relative - (int)ta->_eye_relative; } + if (_fixed_depth != ta->_fixed_depth) { + return (int)_fixed_depth - (int)ta->_fixed_depth; + } if (_offset != ta->_offset) { return _offset < ta->_offset ? -1 : 1; } @@ -274,11 +280,17 @@ compute_billboard(CPT(TransformState) &node_transform, // Also slide the billboard geometry towards the camera according to the // offset factor. - if (_offset != 0.0f) { + if (_offset != 0.0f || _fixed_depth) { LVector3 translate(rel_mat(3, 0), rel_mat(3, 1), rel_mat(3, 2)); + LPoint3 pos; + if (_fixed_depth) { + pos = translate / rel_mat(3, 3); + } else { + pos.fill(0.0f); + } translate.normalize(); translate *= _offset; - rotate.set_row(3, translate); + rotate.set_row(3, pos + translate); } node_transform = translate->compose(TransformState::make_mat(rotate))->compose(node_transform); @@ -307,7 +319,25 @@ write_datagram(BamWriter *manager, Datagram &dg) { dg.add_stdfloat(_offset); _look_at_point.write_datagram(dg); - // *** We don't write out the _look_at NodePath right now. Maybe we should. + if (manager->get_file_minor_ver() >= 43) { + _look_at.write_datagram(manager, dg); + dg.add_bool(_fixed_depth); + } +} + +/** + * Receives an array of pointers, one for each time manager->read_pointer() + * was called in fillin(). Returns the number of pointers processed. + */ +int BillboardEffect:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = RenderEffect::complete_pointers(p_list, manager); + + if (manager->get_file_minor_ver() >= 43) { + pi += _look_at.complete_pointers(p_list + pi, manager); + } + + return pi; } /** @@ -341,4 +371,9 @@ fillin(DatagramIterator &scan, BamReader *manager) { _axial_rotate = scan.get_bool(); _offset = scan.get_stdfloat(); _look_at_point.read_datagram(scan); + + if (manager->get_file_minor_ver() >= 43) { + _look_at.fillin(scan, manager); + _fixed_depth = scan.get_stdfloat(); + } } diff --git a/panda/src/pgraph/billboardEffect.h b/panda/src/pgraph/billboardEffect.h index f83ecc5323..d080cec0d4 100644 --- a/panda/src/pgraph/billboardEffect.h +++ b/panda/src/pgraph/billboardEffect.h @@ -34,7 +34,8 @@ PUBLISHED: bool axial_rotate, PN_stdfloat offset, const NodePath &look_at, - const LPoint3 &look_at_point); + const LPoint3 &look_at_point, + bool fixed_depth = false); INLINE static CPT(RenderEffect) make_axis(); INLINE static CPT(RenderEffect) make_point_eye(); INLINE static CPT(RenderEffect) make_point_world(); @@ -43,6 +44,7 @@ PUBLISHED: INLINE const LVector3 &get_up_vector() const; INLINE bool get_eye_relative() const; INLINE bool get_axial_rotate() const; + INLINE bool get_fixed_depth() const; INLINE PN_stdfloat get_offset() const; INLINE const NodePath &get_look_at() const; INLINE const LPoint3 &get_look_at_point() const; @@ -72,9 +74,10 @@ private: private: bool _off; - LVector3 _up_vector; bool _eye_relative; bool _axial_rotate; + bool _fixed_depth; + LVector3 _up_vector; PN_stdfloat _offset; NodePath _look_at; LPoint3 _look_at_point; @@ -82,6 +85,8 @@ private: public: static void register_with_read_factory(); virtual void write_datagram(BamWriter *manager, Datagram &dg); + virtual int complete_pointers(TypedWritable **plist, + BamReader *manager); protected: static TypedWritable *make_from_bam(const FactoryParams ¶ms); diff --git a/panda/src/pgraph/compassEffect.cxx b/panda/src/pgraph/compassEffect.cxx index 41bd9af880..fe39b03bf3 100644 --- a/panda/src/pgraph/compassEffect.cxx +++ b/panda/src/pgraph/compassEffect.cxx @@ -284,8 +284,25 @@ void CompassEffect:: write_datagram(BamWriter *manager, Datagram &dg) { RenderEffect::write_datagram(manager, dg); dg.add_uint16(_properties); - // *** We don't write out the _reference NodePath right now. Maybe we - // should. + + if (manager->get_file_minor_ver() >= 43) { + _reference.write_datagram(manager, dg); + } +} + +/** + * Receives an array of pointers, one for each time manager->read_pointer() + * was called in fillin(). Returns the number of pointers processed. + */ +int CompassEffect:: +complete_pointers(TypedWritable **p_list, BamReader *manager) { + int pi = RenderEffect::complete_pointers(p_list, manager); + + if (manager->get_file_minor_ver() >= 43) { + pi += _reference.complete_pointers(p_list + pi, manager); + } + + return pi; } /** @@ -313,4 +330,8 @@ void CompassEffect:: fillin(DatagramIterator &scan, BamReader *manager) { RenderEffect::fillin(scan, manager); _properties = scan.get_uint16(); + + if (manager->get_file_minor_ver() >= 43) { + _reference.fillin(scan, manager); + } } diff --git a/panda/src/pgraph/compassEffect.h b/panda/src/pgraph/compassEffect.h index adba81f9e6..a8aada7a59 100644 --- a/panda/src/pgraph/compassEffect.h +++ b/panda/src/pgraph/compassEffect.h @@ -90,6 +90,8 @@ private: public: static void register_with_read_factory(); virtual void write_datagram(BamWriter *manager, Datagram &dg); + virtual int complete_pointers(TypedWritable **plist, + BamReader *manager); protected: static TypedWritable *make_from_bam(const FactoryParams ¶ms); diff --git a/panda/src/pgraph/nodePath.I b/panda/src/pgraph/nodePath.I index 5b7e80f7b7..8ca3153da5 100644 --- a/panda/src/pgraph/nodePath.I +++ b/panda/src/pgraph/nodePath.I @@ -1752,7 +1752,7 @@ set_billboard_axis(PN_stdfloat offset) { * the camera. */ INLINE void NodePath:: -set_billboard_point_eye(PN_stdfloat offset) { +set_billboard_point_eye(PN_stdfloat offset, bool fixed_depth) { set_billboard_point_eye(NodePath(), offset); } diff --git a/panda/src/pgraph/nodePath.cxx b/panda/src/pgraph/nodePath.cxx index 0f2639bccc..9d473304b6 100644 --- a/panda/src/pgraph/nodePath.cxx +++ b/panda/src/pgraph/nodePath.cxx @@ -4744,11 +4744,11 @@ set_billboard_axis(const NodePath &camera, PN_stdfloat offset) { * the camera, towards a specified "camera" instead of to the viewing camera. */ void NodePath:: -set_billboard_point_eye(const NodePath &camera, PN_stdfloat offset) { +set_billboard_point_eye(const NodePath &camera, PN_stdfloat offset, bool fixed_depth) { nassertv_always(!is_empty()); CPT(RenderEffect) billboard = BillboardEffect::make (LVector3::up(), true, false, - offset, camera, LPoint3(0.0f, 0.0f, 0.0f)); + offset, camera, LPoint3(0.0f, 0.0f, 0.0f), fixed_depth); node()->set_effect(billboard); } diff --git a/panda/src/pgraph/nodePath.h b/panda/src/pgraph/nodePath.h index cf159972c5..9c047c0728 100644 --- a/panda/src/pgraph/nodePath.h +++ b/panda/src/pgraph/nodePath.h @@ -815,10 +815,10 @@ PUBLISHED: void do_billboard_point_eye(const NodePath &camera, PN_stdfloat offset); void do_billboard_point_world(const NodePath &camera, PN_stdfloat offset); INLINE void set_billboard_axis(PN_stdfloat offset = 0.0); - INLINE void set_billboard_point_eye(PN_stdfloat offset = 0.0); + INLINE void set_billboard_point_eye(PN_stdfloat offset = 0.0, bool fixed_depth = false); INLINE void set_billboard_point_world(PN_stdfloat offset = 0.0); void set_billboard_axis(const NodePath &camera, PN_stdfloat offset); - void set_billboard_point_eye(const NodePath &camera, PN_stdfloat offset); + void set_billboard_point_eye(const NodePath &camera, PN_stdfloat offset, bool fixed_depth = false); void set_billboard_point_world(const NodePath &camera, PN_stdfloat offset); void clear_billboard(); bool has_billboard() const; diff --git a/panda/src/putil/bam.h b/panda/src/putil/bam.h index ad7d000fd2..96f41cbf98 100644 --- a/panda/src/putil/bam.h +++ b/panda/src/putil/bam.h @@ -32,7 +32,7 @@ static const unsigned short _bam_major_ver = 6; // Bumped to major version 6 on 2006-02-11 to factor out PandaNode::CData. static const unsigned short _bam_first_minor_ver = 14; -static const unsigned short _bam_minor_ver = 42; +static const unsigned short _bam_minor_ver = 43; // Bumped to minor version 14 on 2007-12-19 to change default ColorAttrib. // Bumped to minor version 15 on 2008-04-09 to add TextureAttrib::_implicit_sort. // Bumped to minor version 16 on 2008-05-13 to add Texture::_quality_level. @@ -62,5 +62,6 @@ static const unsigned short _bam_minor_ver = 42; // Bumped to minor version 40 on 2016-01-11 to make NodePaths writable. // Bumped to minor version 41 on 2016-03-02 to change LensNode, Lens, and Camera. // Bumped to minor version 42 on 2016-04-08 to expand ColorBlendAttrib. +// Bumped to minor version 43 on 2018-12-06 to expand BillboardEffect and CompassEffect. #endif From ca9ad5f1740c1235b9ddbdbbe38027d6bcf7a9b8 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 7 Dec 2018 00:57:21 +0100 Subject: [PATCH 318/424] pgraph: fix ignored fixed_depth parameter --- panda/src/pgraph/nodePath.I | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/pgraph/nodePath.I b/panda/src/pgraph/nodePath.I index 8ca3153da5..1e0c6b3ab0 100644 --- a/panda/src/pgraph/nodePath.I +++ b/panda/src/pgraph/nodePath.I @@ -1753,7 +1753,7 @@ set_billboard_axis(PN_stdfloat offset) { */ INLINE void NodePath:: set_billboard_point_eye(PN_stdfloat offset, bool fixed_depth) { - set_billboard_point_eye(NodePath(), offset); + set_billboard_point_eye(NodePath(), offset, fixed_depth); } /** From 09bbee8deae2671ab41406e67e98070bda5186ac Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 8 Dec 2018 16:19:35 -0800 Subject: [PATCH 319/424] deploy-ng: Default platforms to a list of platforms instead of the host platform The list is: * manylinux1_x86_64 * macosx_10_6_x86_64 * win_amd64 Of note, win32 is missing from this list. We can add it later if there is desire. Also of note, there is now no path to *not* using wheels. We will need to evaluate if we want to keep this around and, if so, expose it as an option to users. --- direct/src/showutil/dist.py | 21 ++++++++------------- samples/asteroids/setup.py | 6 ------ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 89ec394d6c..2df47596c7 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -206,7 +206,11 @@ class build_apps(setuptools.Command): self.exclude_patterns = [] self.include_modules = {} self.exclude_modules = {} - self.platforms = [] + self.platforms = [ + 'manylinux1_x86_64', + 'macosx_10_6_x86_64', + 'win_amd64', + ] self.plugins = [] self.embed_prc_data = True self.extra_prc_files = [] @@ -323,19 +327,10 @@ class build_apps(setuptools.Command): self.package_data_dirs = tmp def run(self): - if not self.platforms: - platforms = [p3d.PandaSystem.get_platform()] - use_wheels = False - elif isinstance(self.platforms, basestring): - platforms = [self.platforms] - use_wheels = True - else: - platforms = self.platforms - use_wheels = True - self.announce('Building platforms: {0}'.format(','.join(platforms)), distutils.log.INFO) + self.announce('Building platforms: {0}'.format(','.join(self.platforms)), distutils.log.INFO) - for platform in platforms: - self.build_runtimes(platform, use_wheels) + for platform in self.platforms: + self.build_runtimes(platform, True) def download_wheels(self, platform): """ Downloads wheels for the given platform using pip. This includes panda3d diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 8f7fd76902..955e68b947 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -18,12 +18,6 @@ setup( 'pandagl', 'p3openal_audio', ], - 'platforms': [ - 'manylinux1_x86_64', - 'macosx_10_6_x86_64', - 'win32', - 'win_amd64', - ], } } ) From a5ad186a663c15d1d3cf3832a3c2ee95417ee917 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 8 Dec 2018 17:21:59 -0800 Subject: [PATCH 320/424] x11display: Do not grab the mouse in confined mode when the window does not have focus --- panda/src/x11display/x11GraphicsWindow.cxx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index caaacc4a9c..ad0f8e44dd 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -508,6 +508,26 @@ process_events() { changed_properties = true; } + if (properties.has_foreground() && _properties.get_mouse_mode() == WindowProperties::M_confined) { + // Focus has changed, let's let go of the pointer if we've grabbed or re-grab it if needed + if (properties.get_foreground()) { + // Window is going to the foreground, re-grab the pointer + X11_Cursor cursor = None; + if (_properties.get_cursor_hidden()) { + x11GraphicsPipe *x11_pipe; + DCAST_INTO_V(x11_pipe, _pipe); + cursor = x11_pipe->get_hidden_cursor(); + } + + XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, GrabModeAsync, + _xwindow, cursor, CurrentTime); + } + else { + // window is leaving the foreground, ungrab the pointer + XUngrabPointer(_display, CurrentTime); + } + } + if (changed_properties) { system_changed_properties(properties); } From 825058805a02cf05480aa830326b096df5cd36aa Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 15:18:09 +0100 Subject: [PATCH 321/424] dtoolutil: add GlobPattern::matches_file() and unit test This is used similar to matches() but for file paths: it does not match slashes for * characters and has special support for /**/ and ignores // and /./ patterns. --- dtool/src/dtoolutil/globPattern.cxx | 125 +++++++++++++++++++++++++++- dtool/src/dtoolutil/globPattern.h | 2 + tests/dtoolutil/test_globpattern.py | 85 +++++++++++++++++++ 3 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 tests/dtoolutil/test_globpattern.py diff --git a/dtool/src/dtoolutil/globPattern.cxx b/dtool/src/dtoolutil/globPattern.cxx index 982fe3bb1d..d2ea916466 100644 --- a/dtool/src/dtoolutil/globPattern.cxx +++ b/dtool/src/dtoolutil/globPattern.cxx @@ -12,6 +12,7 @@ */ #include "globPattern.h" +#include "string_utils.h" #include using std::string; @@ -199,9 +200,7 @@ r_match_files(const Filename &prefix, const string &suffix, next_glob = *this; } - vector_string::const_iterator fi; - for (fi = dir_files.begin(); fi != dir_files.end(); ++fi) { - const string &local_file = (*fi); + for (const string &local_file : dir_files) { if (_pattern[0] == '.' || (local_file.empty() || local_file[0] != '.')) { if (matches(local_file)) { // We have a match; continue. @@ -227,6 +226,126 @@ r_match_files(const Filename &prefix, const string &suffix, return num_matched; } +/** + * Treats the GlobPattern as a filename pattern, and returns true if the given + * filename matches the pattern. Unlike matches(), this will not match slash + * characters for single asterisk characters, and it will ignore path + * components that only contain a dot. + */ +bool GlobPattern:: +matches_file(Filename candidate) const { + if (_pattern.empty()) { + // Special case. + return candidate.empty(); + } + + // Either both must be absolute, or both must be relative. + if ((_pattern[0] != '/') != candidate.is_local()) { + return false; + } + + return r_matches_file(_pattern, candidate); +} + +/** + * The recursive implementation of matches_file(). + */ +bool GlobPattern:: +r_matches_file(const string &pattern, const Filename &candidate) const { + // Split off the next bit of pattern. + std::string next_pattern; + GlobPattern glob; + glob.set_case_sensitive(get_case_sensitive()); + glob.set_nomatch_chars(get_nomatch_chars()); + + bool pattern_end; + size_t slash = pattern.find('/'); + if (slash == string::npos) { + glob.set_pattern(pattern); + pattern_end = true; + } else { + glob.set_pattern(pattern.substr(0, slash)); + next_pattern = pattern.substr(slash + 1); + pattern_end = false; + + if (slash == 0 || (slash == 1 && pattern[0] == '.')) { + // Ignore // and /./ in patterns + return r_matches_file(next_pattern, candidate); + } + } + + // Also split off the next component in the candidate filename. + std::string part; + Filename next_candidate; + + bool candidate_end; + size_t fn_slash = ((const std::string &)candidate).find('/'); + if (fn_slash == string::npos) { + part = candidate; + candidate_end = true; + } else { + part = candidate.substr(0, fn_slash); + next_candidate = candidate.substr(fn_slash + 1); + candidate_end = false; + + // Ignore // and /./ in filenames. + if (fn_slash == 0 || part == ".") { + return r_matches_file(pattern, next_candidate); + } + } + + // Now check if the current part matches the current pattern. + bool part_matches; + if (glob.get_pattern() == "**") { + // This matches any number of parts. + if (pattern_end) { + // We might as well stop checking here; it matches whatever might come. + return true; + } + // We branch out to three options: either we match nothing, we match this + // part only, or we match this part and maybe more. + return r_matches_file(next_pattern, candidate) + || (!candidate_end && r_matches_file(next_pattern, next_candidate)) + || (!candidate_end && r_matches_file(pattern, next_candidate)); + } + else if (glob.get_pattern() == "*" && _nomatch_chars.empty()) { + // Matches any part (faster version of below) + part_matches = true; + } + else if ((glob.get_pattern() == "." && part.empty()) + || (glob.get_pattern().empty() && part == ".")) { + // So that /path/. matches /path/, and vice versa. + part_matches = true; + } + else if (glob.has_glob_characters()) { + part_matches = glob.matches(part); + } + else if (get_case_sensitive()) { + part_matches = (part == glob.get_pattern()); + } + else { + part_matches = (cmp_nocase(part, glob.get_pattern()) == 0); + } + + if (!part_matches) { + // It doesn't match, so we end our search here. + return false; + } + + if (candidate_end && pattern_end) { + // We've reached the end of both candidate and pattern, so it matches. + return true; + } + + if (pattern_end != candidate_end) { + // One of them has ended, but the other hasn't, so it's not a match. + return false; + } + + // It matches; move on to the next part. + return r_matches_file(next_pattern, next_candidate); +} + /** * The recursive implementation of matches(). This returns true if the * pattern substring [pi, pend) matches the candidate substring [ci, cend), diff --git a/dtool/src/dtoolutil/globPattern.h b/dtool/src/dtoolutil/globPattern.h index 27dfb67777..af181691ab 100644 --- a/dtool/src/dtoolutil/globPattern.h +++ b/dtool/src/dtoolutil/globPattern.h @@ -52,6 +52,7 @@ PUBLISHED: MAKE_PROPERTY(nomatch_chars, get_nomatch_chars, set_nomatch_chars); INLINE bool matches(const std::string &candidate) const; + bool matches_file(Filename candidate) const; INLINE void output(std::ostream &out) const; @@ -74,6 +75,7 @@ private: int r_match_files(const Filename &prefix, const std::string &suffix, vector_string &results, const Filename &cwd); + bool r_matches_file(const std::string &suffix, const Filename &candidate) const; std::string _pattern; bool _case_sensitive; diff --git a/tests/dtoolutil/test_globpattern.py b/tests/dtoolutil/test_globpattern.py new file mode 100644 index 0000000000..d653c97142 --- /dev/null +++ b/tests/dtoolutil/test_globpattern.py @@ -0,0 +1,85 @@ +from panda3d.core import GlobPattern + + +def test_globpattern_matches_file(): + patt = GlobPattern('/a/b/c') + assert patt.matches_file('/a/b/c') + assert patt.matches_file('///a////b//c') + assert patt.matches_file('/a/b/././c') + assert not patt.matches_file('') + assert not patt.matches_file('/') + assert not patt.matches_file('/a/b/d') + assert not patt.matches_file('/A/b/c') + assert not patt.matches_file('/a/b/c/') + assert not patt.matches_file('/a/b/c/.') + assert not patt.matches_file('a/b/c') + assert not patt.matches_file('./a/b/c') + + # Test regular pattern + patt = GlobPattern('*a') + assert patt.matches_file('a') + assert patt.matches_file('aa') + assert patt.matches_file('xa') + assert not patt.matches_file('A') + assert not patt.matches_file('ax') + assert not patt.matches_file('xax') + + # Test path ending in directory + for patt in GlobPattern('/a/b/c/'), \ + GlobPattern('/a/b/c/.'), \ + GlobPattern('/a/b//c//'), \ + GlobPattern('/a/b/./c/./'): + assert patt.matches_file('/a/b/c/') + assert patt.matches_file('///a////b//c//') + assert patt.matches_file('/a/b/././c/') + assert patt.matches_file('/a/b/c/.') + assert not patt.matches_file('/a/b/c') + assert not patt.matches_file('/a/b/c/./d') + assert not patt.matches_file('a/b/c/') + assert not patt.matches_file('./a/b/c/') + + # Test globstar in middle + for patt in GlobPattern('/a/**/c'), GlobPattern('/a/**/**/c'): + assert patt.matches_file('/a/c') + assert patt.matches_file('/a/b/c') + assert patt.matches_file('/a/b/d/c') + assert not patt.matches_file('/a/b/c/d') + assert not patt.matches_file('/d/b/c') + assert not patt.matches_file('/a/b/d') + + # Test globstar in beginning + for patt in GlobPattern('/**/b/c'), GlobPattern('/**/**/**/b/c'): + assert patt.matches_file('/a/b/c') + assert patt.matches_file('/a/d/b/c') + assert patt.matches_file('/a/b/c') + assert patt.matches_file('/a/b/c/./b//c') + assert not patt.matches_file('/a/b/c/d') + assert not patt.matches_file('/a/c') + assert not patt.matches_file('/a/b/d') + + # Test globstar at end + for patt in GlobPattern('/a/b/**'), \ + GlobPattern('/a/b/**/**'), \ + GlobPattern('/a/b//**//**/**'): + assert patt.matches_file('/a/b/') + assert patt.matches_file('/a/b/.') + assert patt.matches_file('/a/b//') + assert patt.matches_file('/a/b/c') + assert patt.matches_file('/a/b/c/d/e/f/g/h') + assert patt.matches_file('/a/b/d/c') + assert not patt.matches_file('/a/') + assert not patt.matches_file('/a/c/b') + + # Test multiple globstars at multiple locations + patt = GlobPattern('/a/**/b/**/c') + assert patt.matches_file('/a/b/c') + assert patt.matches_file('/a/./b/./c') + assert patt.matches_file('/a//b//c') + assert patt.matches_file('/a/x/y/b/c') + assert patt.matches_file('/a/b/x/y/c') + assert patt.matches_file('/a/b/c/a/b/c') + assert patt.matches_file('/a/x/y/b/x/y/c') + assert not patt.matches_file('/a/b/x') + assert not patt.matches_file('/a/b/c/x') + assert not patt.matches_file('/a/b/c/') + assert not patt.matches_file('/a/b/c/.') From 4213e0a50a2190c479959ce87b976173f50c536b Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 15:39:28 +0100 Subject: [PATCH 322/424] Remove awesomium (see #440) --- doc/INSTALL | 1 - makepanda/installer.nsi | 1 - makepanda/makepanda.py | 33 +-- makepanda/makepanda.vcproj | 19 -- panda/src/awesomium/AwMouseAndKeyboard.cxx | 47 ----- panda/src/awesomium/AwMouseAndKeyboard.h | 61 ------ panda/src/awesomium/ReadMe.txt | 8 - panda/src/awesomium/WebBrowserTexture.cxx | 194 ------------------ panda/src/awesomium/WebBrowserTexture.h | 77 ------- panda/src/awesomium/awWebCore.I | 48 ----- panda/src/awesomium/awWebCore.cxx | 56 ----- panda/src/awesomium/awWebCore.h | 94 --------- panda/src/awesomium/awWebView.I | 82 -------- panda/src/awesomium/awWebView.cxx | 74 ------- panda/src/awesomium/awWebView.h | 131 ------------ panda/src/awesomium/awWebViewListener.I | 12 -- panda/src/awesomium/awWebViewListener.cxx | 80 -------- panda/src/awesomium/awWebViewListener.h | 123 ----------- panda/src/awesomium/awesomium_includes.h | 21 -- panda/src/awesomium/config_awesomium.cxx | 51 ----- panda/src/awesomium/config_awesomium.h | 26 --- .../awesomium/pandaawesomium_composite1.cxx | 4 - panda/src/pandabase/pandasymbols.h | 8 - 23 files changed, 1 insertion(+), 1250 deletions(-) delete mode 100644 panda/src/awesomium/AwMouseAndKeyboard.cxx delete mode 100644 panda/src/awesomium/AwMouseAndKeyboard.h delete mode 100644 panda/src/awesomium/ReadMe.txt delete mode 100644 panda/src/awesomium/WebBrowserTexture.cxx delete mode 100644 panda/src/awesomium/WebBrowserTexture.h delete mode 100644 panda/src/awesomium/awWebCore.I delete mode 100644 panda/src/awesomium/awWebCore.cxx delete mode 100644 panda/src/awesomium/awWebCore.h delete mode 100644 panda/src/awesomium/awWebView.I delete mode 100644 panda/src/awesomium/awWebView.cxx delete mode 100644 panda/src/awesomium/awWebView.h delete mode 100644 panda/src/awesomium/awWebViewListener.I delete mode 100644 panda/src/awesomium/awWebViewListener.cxx delete mode 100644 panda/src/awesomium/awWebViewListener.h delete mode 100644 panda/src/awesomium/awesomium_includes.h delete mode 100644 panda/src/awesomium/config_awesomium.cxx delete mode 100644 panda/src/awesomium/config_awesomium.h delete mode 100644 panda/src/awesomium/pandaawesomium_composite1.cxx diff --git a/doc/INSTALL b/doc/INSTALL index 4157e03957..6a9606391e 100644 --- a/doc/INSTALL +++ b/doc/INSTALL @@ -194,7 +194,6 @@ it will show you the available command-line options: --use-wx --no-wx (enable/disable use of WX) --use-fltk --no-fltk (enable/disable use of FLTK) --use-rocket --no-rocket (enable/disable use of ROCKET) - --use-awesomium --no-awesomium (enable/disable use of AWESOMIUM) --use-carbon --no-carbon (enable/disable use of CARBON) --use-cocoa --no-cocoa (enable/disable use of COCOA) --use-x11 --no-x11 (enable/disable use of X11) diff --git a/makepanda/installer.nsi b/makepanda/installer.nsi index fc10ce927c..8bc608cf6f 100644 --- a/makepanda/installer.nsi +++ b/makepanda/installer.nsi @@ -333,7 +333,6 @@ SectionGroup "Python support" File /nonfatal /r "${BUILT}\panda3d\core${EXT_SUFFIX}" File /nonfatal /r "${BUILT}\panda3d\ai${EXT_SUFFIX}" - File /nonfatal /r "${BUILT}\panda3d\awesomium${EXT_SUFFIX}" File /nonfatal /r "${BUILT}\panda3d\direct${EXT_SUFFIX}" File /nonfatal /r "${BUILT}\panda3d\egg${EXT_SUFFIX}" File /nonfatal /r "${BUILT}\panda3d\fx${EXT_SUFFIX}" diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 43c42e5fd9..ff6787f987 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -91,7 +91,7 @@ PkgListSet(["PYTHON", "DIRECT", # Python support "ARTOOLKIT", "OPENCV", "DIRECTCAM", "VISION", # Augmented Reality "GTK2", # GTK2 is used for PStats on Unix "MFC", "WX", "FLTK", # Used for web plug-in only - "ROCKET", "AWESOMIUM", # GUI libraries + "ROCKET", # GUI libraries "CARBON", "COCOA", # Mac OS X toolkits "X11", # Unix platform support "PANDATOOL", "PVIEW", "DEPLOYTOOLS", # Toolchain @@ -677,7 +677,6 @@ if (COMPILER == "MSVC"): if (PkgSkip("OPENCV")==0): LibName("OPENCV", GetThirdpartyDir() + "opencv/lib/cvaux.lib") if (PkgSkip("OPENCV")==0): LibName("OPENCV", GetThirdpartyDir() + "opencv/lib/ml.lib") if (PkgSkip("OPENCV")==0): LibName("OPENCV", GetThirdpartyDir() + "opencv/lib/cxcore.lib") - if (PkgSkip("AWESOMIUM")==0):LibName("AWESOMIUM",GetThirdpartyDir() + "awesomium/lib/Awesomium.lib") if (PkgSkip("FFMPEG")==0): LibName("FFMPEG", GetThirdpartyDir() + "ffmpeg/lib/avcodec.lib") if (PkgSkip("FFMPEG")==0): LibName("FFMPEG", GetThirdpartyDir() + "ffmpeg/lib/avformat.lib") if (PkgSkip("FFMPEG")==0): LibName("FFMPEG", GetThirdpartyDir() + "ffmpeg/lib/avutil.lib") @@ -797,7 +796,6 @@ if (COMPILER == "MSVC"): LibName("BULLET", GetThirdpartyDir() + "bullet/lib/BulletSoftBody" + suffix) if (COMPILER=="GCC"): - PkgDisable("AWESOMIUM") if GetTarget() != "darwin": PkgDisable("CARBON") PkgDisable("COCOA") @@ -2850,8 +2848,6 @@ if not PkgSkip("SKEL"): panda_modules.append('skel') if not PkgSkip("EGG"): panda_modules.append('egg') -if not PkgSkip("AWESOMIUM"): - panda_modules.append('awesomium') if not PkgSkip("ODE"): panda_modules.append('ode') if not PkgSkip("VRPN"): @@ -3197,7 +3193,6 @@ CopyAllHeaders('panda/src/text') CopyAllHeaders('panda/src/grutil') if (PkgSkip("VISION")==0): CopyAllHeaders('panda/src/vision') -CopyAllHeaders('panda/src/awesomium') if (PkgSkip("FFMPEG")==0): CopyAllHeaders('panda/src/ffmpeg') CopyAllHeaders('panda/src/tform') @@ -4372,32 +4367,6 @@ if (PkgSkip("ROCKET") == 0) and (not RUNTIME): PyTargetAdd('rocket.pyd', input=COMMON_PANDA_LIBS) PyTargetAdd('rocket.pyd', opts=['ROCKET']) -# -# DIRECTORY: panda/src/p3awesomium -# -if PkgSkip("AWESOMIUM") == 0 and not RUNTIME: - OPTS=['DIR:panda/src/awesomium', 'BUILDING:PANDAAWESOMIUM', 'AWESOMIUM'] - TargetAdd('pandaawesomium_composite1.obj', opts=OPTS, input='pandaawesomium_composite1.cxx') - TargetAdd('libp3awesomium.dll', input='pandaawesomium_composite1.obj') - TargetAdd('libp3awesomium.dll', input=COMMON_PANDA_LIBS) - TargetAdd('libp3awesomium.dll', opts=OPTS) - - OPTS=['DIR:panda/src/awesomium', 'AWESOMIUM'] - IGATEFILES=GetDirectoryContents('panda/src/awesomium', ["*.h", "*_composite1.cxx"]) - TargetAdd('libp3awesomium.in', opts=OPTS, input=IGATEFILES) - TargetAdd('libp3awesomium.in', opts=['IMOD:panda3d.awesomium', 'ILIB:libp3awesomium', 'SRCDIR:panda/src/awesomium']) - - - PyTargetAdd('awesomium_module.obj', input='libp3awesomium.in') - PyTargetAdd('awesomium_module.obj', opts=OPTS) - PyTargetAdd('awesomium_module.obj', opts=['IMOD:panda3d.awesomium', 'ILIB:awesomium', 'IMPORT:panda3d.core']) - - PyTargetAdd('awesomium.pyd', input='awesomium_module.obj') - PyTargetAdd('awesomium.pyd', input='libp3awesomium_igate.obj') - PyTargetAdd('awesomium.pyd', input='libp3awesomium.dll') - PyTargetAdd('awesomium.pyd', input='libp3interrogatedb.dll') - PyTargetAdd('awesomium.pyd', input=COMMON_PANDA_LIBS) - # # DIRECTORY: panda/src/p3skel # diff --git a/makepanda/makepanda.vcproj b/makepanda/makepanda.vcproj index c4c34db4e3..ad7f219650 100644 --- a/makepanda/makepanda.vcproj +++ b/makepanda/makepanda.vcproj @@ -3578,25 +3578,6 @@ - - - - - - - - - - - - - - - - - - - diff --git a/panda/src/awesomium/AwMouseAndKeyboard.cxx b/panda/src/awesomium/AwMouseAndKeyboard.cxx deleted file mode 100644 index 9948c0d49d..0000000000 --- a/panda/src/awesomium/AwMouseAndKeyboard.cxx +++ /dev/null @@ -1,47 +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 AwMouseAndKeyboard.cxx - * @author Bei Yang - * @date 2010-03 - */ - -#include "config_awesomium.h" -#include "AwMouseAndKeyboard.h" -#include "dataNodeTransmit.h" - -TypeHandle AwMouseAndKeyboard::_type_handle; - -AwMouseAndKeyboard::AwMouseAndKeyboard(const std::string &name): -DataNode(name) -{ - _button_events_input = define_input("button_events", ButtonEventList::get_class_type()); - _button_events_output = define_output("button_events", ButtonEventList::get_class_type()); -} - - -void AwMouseAndKeyboard::do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input, DataNodeTransmit &output){ - - if (input.has_data(_button_events_input)) { - const ButtonEventList *button_events; - DCAST_INTO_V(button_events, input.get_data(_button_events_input).get_ptr()); - - int num_events = button_events->get_num_events(); - for (int i = 0; i < num_events; i++) { - const ButtonEvent &be = button_events->get_event(i); - std::string event_name = be._button.get_name(); - printf("Button Event! : %s with code %i and index %i ", event_name.c_str(), be._keycode, be._button.get_index()); - if(be._type == ButtonEvent::T_down) printf("down"); - if(be._type == ButtonEvent::T_repeat) printf("repeat"); - if(be._type == ButtonEvent::T_up) printf("up"); - if(be._type == ButtonEvent::T_resume_down) printf("T_resume_down"); - printf("\n"); - } - } - -} diff --git a/panda/src/awesomium/AwMouseAndKeyboard.h b/panda/src/awesomium/AwMouseAndKeyboard.h deleted file mode 100644 index e338939001..0000000000 --- a/panda/src/awesomium/AwMouseAndKeyboard.h +++ /dev/null @@ -1,61 +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 AwMouseAndKeyboard.h - * @author rurbino - * @date 2009-10-12 - */ - -#ifndef AWWEBKEYBOARDMOUSE_H -#define AWWEBKEYBOARDMOUSE_H - -#include "pandabase.h" -#include "typedReferenceCount.h" -#include "luse.h" - -#include "mouseAndKeyboard.h" - - -/** - * Thin wrappings arround WebCore.h - */ -class EXPCL_PANDAAWESOMIUM AwMouseAndKeyboard : public DataNode { -// member data data -protected: - // inputs adn output indices... initialized in constructor - int _button_events_input; - int _button_events_output; - -PUBLISHED: - AwMouseAndKeyboard(const std::string &name); - -protected: - // Inherited from DataNode - virtual void do_transmit_data(DataGraphTraverser *trav, - const DataNodeTransmit &input, - DataNodeTransmit &output); - -public: - static TypeHandle get_class_type() { - return _type_handle; - } - static void init_type() { - MouseAndKeyboard::init_type(); - register_type(_type_handle, "AwMouseAndKeyboard", - 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 diff --git a/panda/src/awesomium/ReadMe.txt b/panda/src/awesomium/ReadMe.txt deleted file mode 100644 index faa064455a..0000000000 --- a/panda/src/awesomium/ReadMe.txt +++ /dev/null @@ -1,8 +0,0 @@ -See http://www.sirikata.com/wiki/index.php?title=Compiling_Awesomium - -But the Chromium revision 22725. -The awesomium git repository used is at: http://github.com/pathorn/awesomium/network -The version of awesomium is from the master August 7 checkin, tree fc3239b4f49031285682cb5d78256d56e9001b66 - -Any method that had a wstring in the parameter, a second method was created which just accepts all std::string. This fixes the VC7 - VC9 linker error. - diff --git a/panda/src/awesomium/WebBrowserTexture.cxx b/panda/src/awesomium/WebBrowserTexture.cxx deleted file mode 100644 index 1051a33e36..0000000000 --- a/panda/src/awesomium/WebBrowserTexture.cxx +++ /dev/null @@ -1,194 +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 WebBrowserTexture.cxx - * @author bei yang - * @date 2010-03 - */ - -#include "config_awesomium.h" -#include "WebBrowserTexture.h" - -TypeHandle WebBrowserTexture::_type_handle; - -/** - * Copy constructor for web browser texture. The behavior of copying a - * webtexture is that will be the same as a standard texture copy. However, - * the content will remain the system until set_web_view is called. - */ -WebBrowserTexture::WebBrowserTexture(const WebBrowserTexture ©): -Texture(copy) -{ - // this kind of assumes that the previous texture was initialized properly - _aw_web_view = copy._aw_web_view; - _update_active = copy._update_active; - _flip_texture_active = copy._flip_texture_active; -} - -/** - * This initializes a web browser texture with the given AwWebView class. - */ -WebBrowserTexture::WebBrowserTexture(const std::string &name, AwWebView* aw_web_view): -Texture(name), -_update_active(true), -_flip_texture_active(false) -{ - set_web_view(aw_web_view); - set_minfilter(FT_linear); - set_magfilter(FT_linear); - -} - -/** - * Standard destructor... doesn't do anything. All destructing happens in - * parent texture class. - */ -WebBrowserTexture::~WebBrowserTexture() -{ - // do nothing -} - - -/** - * Standard destructor... doesn't do anything. All destructing happens in - * parent texture class. - */ -bool WebBrowserTexture::get_keep_ram_image() const { - return true; -} - -/** - * A WebBrowserTexture must always keep its ram image. This is essentially a - * sub. - */ -void WebBrowserTexture::do_reload_ram_image() { - // A MovieTexture should never dump its RAM image. Therefore, this is not - // needed. -} - -/** - * Should be overridden by derived classes to return true if cull_callback() - * has been defined. Otherwise, returns false to indicate cull_callback() - * does not need to be called for this node during the cull traversal. - * - * This one returns true because it uses the cull traverser method to do the - * texture udpate. - */ -bool WebBrowserTexture::has_cull_callback() const { - return true; -} - -/** - * Sets the internal AwWebView of this texture. After calling this, the - * texture will automatically set it's width and height to match the AwWebView - * at the next time it is culled and rendered. - */ -void WebBrowserTexture::set_web_view(AwWebView* aw_web_view){ - _aw_web_view = aw_web_view; -} - - -/** - * Gets the current internal AwWebView of this texture. - */ -AwWebView* WebBrowserTexture::get_web_view() const{ - return _aw_web_view; -} - - -/** - * Gives the ability to toggle updating this texture or not. This can be - * disabled to improve performance so that only the one that needs to be - * active is active. - */ -void WebBrowserTexture::set_update_active(bool active_flag){ - _update_active = active_flag; -} - - -/** - * Gets whether or not this texture is updating itself every time it is - * rendered. - */ -bool WebBrowserTexture::get_update_active() const{ - return _update_active; -} - - -/** - * This toggles on/off automatic flipping of the of the texture at a source - * level. Awesomium renders things that are flipped vertically. This enables - * automatic flipping of that. - * - * Since it is doing byte manipulation, this can get rather slow. Turning - * this on should be avoided. Instead, flipping should be taken care of via - * UV coordinates or shaders. - */ -void WebBrowserTexture::set_flip_texture_active(bool active_flag){ - _flip_texture_active = active_flag; -} - -/** - * Returns whether automatic texture flipping is enabled. - */ -bool WebBrowserTexture::get_flip_texture_active() const { - return _flip_texture_active; -} - - -/** - * This function will be called during the cull traversal to update the - * WebBrowserTexture. This method calls the render method of AwWebView but - * does not call the update method of AwWebCore. - */ -bool WebBrowserTexture::cull_callback(CullTraverser *trav, const CullTraverserData &data) const{ - // see if we are in a state where udpates can happen. else just return - if( !_update_active ) return true; - if( _aw_web_view == nullptr ) return true; - - // do we even need to update? - if( !_aw_web_view->is_dirty() ) return true; - - // see if we're the same size, if not we need to make sure this texture - // matches the webview - if( _aw_web_view->get_width() != get_x_size() || _aw_web_view->get_height() != get_y_size() || get_texture_type() != TT_2d_texture){ - // these casts are so dirty especially when the method itself is - // labled as const. Really Texture::cull_callback should be not const - // first clean up - ((WebBrowserTexture*)this)->clear_ram_mipmap_images(); - ((WebBrowserTexture*)this)->clear_ram_image(); - // now set up the texture again - ((WebBrowserTexture*)this)->setup_2d_texture( _aw_web_view->get_width(), _aw_web_view->get_height(), T_unsigned_byte, F_rgba ); - // should be good to go at this point - } - - // get the pointer - PTA_uchar ram_image = ((WebBrowserTexture*)this)->modify_ram_image(); - unsigned char* cp_data = ram_image.p(); - // render it - _aw_web_view->render((void*)cp_data, get_x_size()*4, 4); - - if(_flip_texture_active){ - // flips the texture around... this is super slow. Really this should - // never be enabled. However beginners might find this useful - size_t width = get_x_size(); - size_t height = get_y_size(); - for(size_t i=0; i < height/2; i++){ - for(size_t j=0; j < width; j++){ - unsigned char tmp[4]; - size_t a_pos = j+width*i; - size_t b_pos = j + width*(height-i-1); - memcpy(tmp,&cp_data[4*a_pos], 4); //tmp = a - memcpy(&cp_data[4*a_pos], &cp_data[4*b_pos], 4); //a = b - memcpy(&cp_data[4*b_pos], tmp, 4); //b = tmp - } - } - } - // success - return true; -} diff --git a/panda/src/awesomium/WebBrowserTexture.h b/panda/src/awesomium/WebBrowserTexture.h deleted file mode 100644 index 3c9e6aaa08..0000000000 --- a/panda/src/awesomium/WebBrowserTexture.h +++ /dev/null @@ -1,77 +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 WebBrowserTexture.h - * @author Bei Yang - * @date 2010-08-03 - */ - -#ifndef WebBrowserTexture_H -#define WebBrowserTexture_H - -#include "pandabase.h" -#include "texture.h" -#include "awWebView.h" - - -/** - * A Wrapper class for Awesomium webview. This implements most of Awesomium's - * features and updates on the cull_traverser callback much much like a movie - * texture. - * - * The use of class means that you will have to follow Awesomium license - * agreement give below http://www.khrona.com/products/awesomium/licensing - */ -class EXPCL_PANDAAWESOMIUM WebBrowserTexture : public Texture { -protected: - AwWebView* _aw_web_view; - bool _update_active; - bool _flip_texture_active; - -// Constructors & Destructors ------------ -private: - WebBrowserTexture(const WebBrowserTexture ©); -PUBLISHED: - WebBrowserTexture(const std::string &name, AwWebView* aw_web_view = nullptr); - virtual ~WebBrowserTexture(); - - -// methods -------------- -protected: - bool get_keep_ram_image() const; - void do_reload_ram_image(); -public: - virtual bool has_cull_callback() const; - virtual bool cull_callback(CullTraverser *trav, const CullTraverserData &data) const; -PUBLISHED: - void set_web_view(AwWebView* aw_web_view); - AwWebView* get_web_view() const; - void set_update_active(bool active_flag); - bool get_update_active() const; - void set_flip_texture_active(bool active_flag); - bool get_flip_texture_active() const; - -// Type handles ---------------- -public: - static TypeHandle get_class_type() { - return _type_handle; - } - static void init_type() { - Texture::init_type(); - register_type(_type_handle, "WebBrowserTexture", - Texture::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 diff --git a/panda/src/awesomium/awWebCore.I b/panda/src/awesomium/awWebCore.I deleted file mode 100644 index 7829f6a370..0000000000 --- a/panda/src/awesomium/awWebCore.I +++ /dev/null @@ -1,48 +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 awWebCore.I - * @author rurbino - * @date 2009-10-12 - */ - -INLINE void AwWebCore:: -setBaseDirectory(const std::string& baseDirectory) { - WebCore::setBaseDirectory(baseDirectory); -} - -INLINE void AwWebCore:: -setCustomResponsePage(int statusCode, const std::string& filePath) { - WebCore::setCustomResponsePage(statusCode, filePath); -} - -INLINE void AwWebCore:: -update() { - WebCore::update(); -} - -INLINE const std::string& AwWebCore:: -getBaseDirectory() const { - return WebCore::getBaseDirectory(); -} - -INLINE bool AwWebCore:: -arePluginsEnabled() const { - return WebCore::arePluginsEnabled(); -} - - -INLINE void AwWebCore:: -pause(){ - WebCore::pause(); -} - -INLINE void AwWebCore:: -resume() { - WebCore::resume(); -} diff --git a/panda/src/awesomium/awWebCore.cxx b/panda/src/awesomium/awWebCore.cxx deleted file mode 100644 index adc5989367..0000000000 --- a/panda/src/awesomium/awWebCore.cxx +++ /dev/null @@ -1,56 +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 awWebCore.cxx - * @author rurbino - * @date 2009-10-12 - */ - -#include "config_awesomium.h" -#include "awWebCore.h" - -#include - -TypeHandle AwWebCore::_type_handle; - -AwWebCore:: -AwWebCore(AwWebCore::LogLevel level, bool enablePlugins , AwWebCore::PixelFormat pixelFormat) -#ifndef CPPPARSER -: - WebCore(static_cast(level), enablePlugins, static_cast(pixelFormat)) -#endif - { - awesomium_cat.info() << "constructing webcore\n"; -} - -AwWebCore:: -~AwWebCore() { - awesomium_cat.info() << "destructor webcore\n"; -} - -Awesomium::WebCore& AwWebCore:: -Get() { - return WebCore::Get(); -} - -Awesomium::WebCore* AwWebCore:: -GetPointer() { - return WebCore::GetPointer(); -} - -AwWebView * AwWebCore:: -createWebView(int width, int height, bool isTransparent , bool enableAsyncRendering , int maxAsyncRenderPerSec ) { - Awesomium::WebView * newView = WebCore::createWebView(width, height, isTransparent, enableAsyncRendering, maxAsyncRenderPerSec); - AwWebView * result = new AwWebView(newView); - return result; -} - -AwWebCore::PixelFormat AwWebCore:: -getPixelFormat() const { - return ( static_cast( WebCore::getPixelFormat()) ); -} diff --git a/panda/src/awesomium/awWebCore.h b/panda/src/awesomium/awWebCore.h deleted file mode 100644 index db3160bf21..0000000000 --- a/panda/src/awesomium/awWebCore.h +++ /dev/null @@ -1,94 +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 awWebCore.h - * @author rurbino - * @date 2009-10-12 - */ - -#ifndef AWWEBCORE_H -#define AWWEBCORE_H - -#include "pandabase.h" -#include "typedReferenceCount.h" -#include "luse.h" - -#include "awesomium_includes.h" - -class AwWebView; -/** - * Thin wrappings arround WebCore.h - */ -class EXPCL_PANDAAWESOMIUM AwWebCore : public TypedReferenceCount, public Awesomium::WebCore { -PUBLISHED: - /** - * An enumeration of the three verbosity settings for the Awesomium Log. - */ - enum LogLevel - { - LOG_NONE , // No log is created - LOG_NORMAL , // Logs only errors - LOG_VERBOSE // Logs everything - }; - - /** - * An enumeration of the two output pixel formats that WebView::render will use. - */ - enum PixelFormat - { - PF_BGRA, // BGRA byte ordering [Blue, Green, Red, Alpha] - PF_RGBA // RGBA byte ordering [Red, Green, Blue, Alpha] - }; - - AwWebCore(LogLevel level = LOG_NORMAL, bool enablePlugins = true, PixelFormat pixelFormat = PF_BGRA); - - virtual ~AwWebCore(); - - static Awesomium::WebCore& Get(); - - static Awesomium::WebCore* GetPointer(); - - INLINE void setBaseDirectory(const std::string& baseDirectory); - - AwWebView * createWebView(int width, int height, bool isTransparent = false, bool enableAsyncRendering = false, int maxAsyncRenderPerSec = 70); - - INLINE void setCustomResponsePage(int statusCode, const std::string& filePath); - - INLINE void update(); - - INLINE const std::string& getBaseDirectory() const; - - AwWebCore::PixelFormat getPixelFormat() const; - - INLINE bool arePluginsEnabled() const; - - INLINE void pause(); - - INLINE void resume(); - -public: - static TypeHandle get_class_type() { - return _type_handle; - } - static void init_type() { - TypedReferenceCount::init_type(); - register_type(_type_handle, "AwWebCore", - 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; -}; - -#include "awWebCore.I" - -#endif diff --git a/panda/src/awesomium/awWebView.I b/panda/src/awesomium/awWebView.I deleted file mode 100644 index c7ecf913e7..0000000000 --- a/panda/src/awesomium/awWebView.I +++ /dev/null @@ -1,82 +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 awWebView.I - * @author rurbino - * @date 2009-10-12 - */ - -INLINE void AwWebView:: -destroy(void) -{ - _myWebView->destroy(); -} - - -INLINE void AwWebView:: -setListener(Awesomium::WebViewListener * listener) { - _myWebView->setListener(listener); -} - -INLINE Awesomium::WebViewListener* AwWebView:: -getListener() { - return _myWebView->getListener(); -} - -INLINE void AwWebView:: -goToHistoryOffset(int offset) { - _myWebView->goToHistoryOffset(offset); -} - -INLINE void AwWebView:: -executeJavascript2(const std::string& javascript, const std::string& frameName ) { - _myWebView->executeJavascript2(javascript, frameName); -} - -INLINE Awesomium::FutureJSValue AwWebView:: -executeJavascriptWithResult2(const std::string& javascript, const std::string& frameName ) { - return _myWebView->executeJavascriptWithResult2(javascript, frameName); -} - -INLINE void AwWebView:: -setProperty(const std::string& name, const Awesomium::JSValue& value) { - _myWebView->setProperty(name, value); -} - -INLINE void AwWebView:: -setCallback(const std::string& name) { - _myWebView->setCallback(name); -} - -INLINE bool AwWebView:: -isDirty() { - return _myWebView->isDirty(); -} - -INLINE void AwWebView:: -render( size_t destination, int destRowSpan, int destDepth) -{ - _myWebView->render( reinterpret_cast(destination), destRowSpan, destDepth, 0); -} - - - -INLINE void AwWebView:: -injectMouseUp(AwWebView::MouseButton button) { - _myWebView->injectMouseUp(static_cast(button)); -} - -INLINE void AwWebView:: -injectMouseWheelXY(int scrollAmountX, int scrollAmountY){ - _myWebView->injectMouseWheelXY(scrollAmountX, scrollAmountY); -} - -INLINE void AwWebView:: -injectKeyEvent(bool press, int modifiers, int windowsCode, int nativeCode) { - _myWebView->injectKeyEvent(press, modifiers, windowsCode, nativeCode); -} diff --git a/panda/src/awesomium/awWebView.cxx b/panda/src/awesomium/awWebView.cxx deleted file mode 100644 index ebc3a63f89..0000000000 --- a/panda/src/awesomium/awWebView.cxx +++ /dev/null @@ -1,74 +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 awWebView.cxx - * @author rurbino - * @date 2009-10-12 - */ - -#include "config_awesomium.h" -#include "awWebView.h" - -TypeHandle AwWebView::_type_handle; - -AwWebView:: -AwWebView(Awesomium::WebView * webViewPtr) { - _myWebView = webViewPtr; - -} - -AwWebView:: -~AwWebView() { -} - - -void AwWebView:: -loadURL2(const std::string& url, const std::string& frameName , const std::string& username , const std::string& password ) -{ - _myWebView->loadURL2(url, frameName, username, password); - -} - -void AwWebView:: -loadHTML2(const std::string& html, const std::string& frameName ) -{ - _myWebView->loadHTML2(html, frameName); -} - - -void AwWebView:: -loadFile2(const std::string& file, const std::string& frameName ) -{ - _myWebView->loadFile2(file, frameName); -} - - -void AwWebView:: -render(size_t destination, int destRowSpan, int destDepth, AwWebView::Rect * renderedRect) { - if (renderedRect) { - Awesomium::Rect rect(renderedRect->x, renderedRect->y, renderedRect->width, renderedRect->height); - _myWebView->Awesomium::WebView::render( reinterpret_cast(destination), destRowSpan, destDepth, &rect); - } - else - { - AwWebView::render(destination, destRowSpan, destDepth, 0); - } -} - -void AwWebView:: -injectMouseDown(AwWebView::MouseButton button) { - awesomium_cat.debug() <<"got mouse down " << button << "\n"; - _myWebView->injectMouseDown(static_cast(button)); -} - - -void AwWebView:: -injectMouseMove(int x, int y) { - // awesomium_cat.debug() <<"got mouse move " << x << " " << y << "\n"; - _myWebView->injectMouseMove(x,y); -} diff --git a/panda/src/awesomium/awWebView.h b/panda/src/awesomium/awWebView.h deleted file mode 100644 index ad5c9d223c..0000000000 --- a/panda/src/awesomium/awWebView.h +++ /dev/null @@ -1,131 +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 awWebView.h - * @author rurbino - * @date 2009-10-12 - */ - -#ifndef AWWEBVIEW_H -#define AWWEBVIEW_H - -#include "pandabase.h" -#include "typedReferenceCount.h" -#include "luse.h" - -#include "awesomium_includes.h" - -class WebViewListener; - -/** - * Thin bindings, wraps a WebView * returned from WebCore.createWebView - */ -class EXPCL_PANDAAWESOMIUM AwWebView : public TypedReferenceCount{ -PUBLISHED: - - /** - * Mouse button enumerations, used with WebView::injectMouseDown - * and WebView::injectMouseUp - */ - enum MouseButton { - LEFT_MOUSE_BTN, - MIDDLE_MOUSE_BTN, - RIGHT_MOUSE_BTN - }; - - /** - * A simple rectangle class, used with WebView::render - */ - struct Rect { - int x, y, width, height; - - Rect(); - Rect(int x, int y, int width, int height); - bool isEmpty() const; - }; - - -PUBLISHED: - AwWebView(Awesomium::WebView * webView); - - virtual ~AwWebView(); - - INLINE void destroy(void); - - INLINE void setListener(Awesomium::WebViewListener * listener); - - INLINE Awesomium::WebViewListener* getListener(); - - // VC7 linker doesn't like wstring from VS2008, hence using the all regular - // string version - void loadURL2(const std::string& url, const std::string& frameName ="", const std::string& username="" , const std::string& password=""); - - // VC7 linker doesn't like wstring from VS2008, hence using the all regular - // string version - void loadHTML2(const std::string& html, const std::string& frameName = ""); - - // VC7 linker doesn't like wstring from VS2008, hence using the all regular - // string version - void loadFile2(const std::string& file, const std::string& frameName = "" ); - - INLINE void goToHistoryOffset(int offset); - - // VC7 linker doesn't like wstring from VS2008, hence using the all regular - // string version - INLINE void executeJavascript2(const std::string& javascript, const std::string& frameName = "" ); - - INLINE Awesomium::FutureJSValue executeJavascriptWithResult2(const std::string& javascript, const std::string& frameName = ""); - - INLINE void setProperty(const std::string& name, const Awesomium::JSValue& value); - - INLINE void setCallback(const std::string& name); - - INLINE bool isDirty(); - - INLINE void render(size_t destination, int destRowSpan, int destDepth); - - void render(size_t destination, int destRowSpan, int destDepth, AwWebView::Rect * renderedRect); - - void injectMouseMove(int x, int y); - - void injectMouseDown(AwWebView::MouseButton button); - - INLINE void injectMouseUp(AwWebView::MouseButton button); - - INLINE void injectMouseWheelXY(int scrollAmountX, int scrollAmountY); - - INLINE void injectMouseWheel(int scrollAmountY) { - injectMouseWheelXY(0, scrollAmountY); - } - - INLINE void injectKeyEvent(bool press, int modifiers, int windowsCode, int nativeCode=0); - -private: - Awesomium::WebView * _myWebView; - -public: - static TypeHandle get_class_type() { - return _type_handle; - } - static void init_type() { - TypedReferenceCount::init_type(); - register_type(_type_handle, "AwWebView", - 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; -}; - -#include "awWebView.I" - -#endif diff --git a/panda/src/awesomium/awWebViewListener.I b/panda/src/awesomium/awWebViewListener.I deleted file mode 100644 index 10b7745aef..0000000000 --- a/panda/src/awesomium/awWebViewListener.I +++ /dev/null @@ -1,12 +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 awWebViewListener.I - * @author rurbino - * @date 2009-10-12 - */ diff --git a/panda/src/awesomium/awWebViewListener.cxx b/panda/src/awesomium/awWebViewListener.cxx deleted file mode 100644 index 0af38b5da8..0000000000 --- a/panda/src/awesomium/awWebViewListener.cxx +++ /dev/null @@ -1,80 +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 awWebViewListener.cxx - * @author rurbino - * @date 2009-10-12 - */ - -#include "config_awesomium.h" -#include "awWebViewListener.h" - -TypeHandle AwWebViewListener::_type_handle; - -AwWebViewListener:: -AwWebViewListener() { - awesomium_cat.info() << "constructing WebViewListner" ; -} - -void AwWebViewListener::onBeginNavigation(const std::string& url, const std::wstring& frameName){ -} - -void AwWebViewListener::onBeginLoading(const std::string& url, const std::wstring& frameName, int statusCode, const std::wstring& mimeType) { - awesomium_cat.info() << "onBeginLoading" ; -} - - /** - * This event is fired when all loads have finished for a WebView. - */ -void AwWebViewListener::onFinishLoading() { -} - - /** - * This event is fired when a Client callback has been invoked via Javascript from a page. - * - * @param name The name of the client callback that was invoked (specifically, "Client._this_name_here_(...)"). - * - * @param args The arguments passed to the callback. - */ -void AwWebViewListener::onCallback(const std::string& name, const Awesomium::JSArguments& args) { -} - - /** - * This event is fired when a page title is received. - * - * @param title The page title. - * - * @param frameName The name of the frame that this event originated from. - */ -void AwWebViewListener::onReceiveTitle(const std::wstring& title, const std::wstring& frameName) { -} - - /** - * This event is fired when a tooltip has changed state. - * - * @param tooltip The tooltip text (or, is an empty string when the tooltip should disappear). - */ -void AwWebViewListener::onChangeTooltip(const std::wstring& tooltip) { -} - - /** - * This event is fired when keyboard focus has changed. - * - * @param isFocused Whether or not the keyboard is currently focused. - */ -void AwWebViewListener::onChangeKeyboardFocus(bool isFocused) { -} - - /** - * This event is fired when the target URL has changed. This is usually the result of - * hovering over a link on the page. - * - * @param url The updated target URL (or empty if the target URL is cleared). - */ -void AwWebViewListener::onChangeTargetURL(const std::string& url) { -} diff --git a/panda/src/awesomium/awWebViewListener.h b/panda/src/awesomium/awWebViewListener.h deleted file mode 100644 index 924754762d..0000000000 --- a/panda/src/awesomium/awWebViewListener.h +++ /dev/null @@ -1,123 +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 awWebViewListener.h - * @author rurbino - * @date 2009-10-12 - */ - -#ifndef AWWEBVIEWLISTENER_H -#define AWWEBVIEWLISTENER_H - -#include "pandabase.h" -#include "typedReferenceCount.h" -#include "luse.h" - -#include "awesomium_includes.h" - -/** - * Thin bindings, wraps a WebViewListener - */ -class EXPCL_PANDAAWESOMIUM AwWebViewListener : public TypedReferenceCount, public Awesomium::WebCore { -PUBLISHED: - - -PUBLISHED: - AwWebViewListener(); - - virtual ~AwWebViewListener() {} - - /** - * This event is fired when a WebView begins navigating to a new URL. - * - * @param url The URL that is being navigated to. - * - * @param frameName The name of the frame that this event originated from. - */ - void onBeginNavigation(const std::string& url, const std::wstring& frameName) ; - - /** - * This event is fired when a WebView begins to actually receive data from a server. - * - * @param url The URL of the frame that is being loaded. - * - * @param frameName The name of the frame that this event originated from. - * - * @param statusCode The HTTP status code returned by the server. - * - * @param mimeType The mime-type of the content that is being loaded. - */ - void onBeginLoading(const std::string& url, const std::wstring& frameName, int statusCode, const std::wstring& mimeType); - - /** - * This event is fired when all loads have finished for a WebView. - */ - void onFinishLoading(); - - /** - * This event is fired when a Client callback has been invoked via Javascript from a page. - * - * @param name The name of the client callback that was invoked (specifically, "Client._this_name_here_(...)"). - * - * @param args The arguments passed to the callback. - */ - void onCallback(const std::string& name, const Awesomium::JSArguments& args); - - /** - * This event is fired when a page title is received. - * - * @param title The page title. - * - * @param frameName The name of the frame that this event originated from. - */ - void onReceiveTitle(const std::wstring& title, const std::wstring& frameName) ; - - /** - * This event is fired when a tooltip has changed state. - * - * @param tooltip The tooltip text (or, is an empty string when the tooltip should disappear). - */ - void onChangeTooltip(const std::wstring& tooltip); - - /** - * This event is fired when keyboard focus has changed. - * - * @param isFocused Whether or not the keyboard is currently focused. - */ - void onChangeKeyboardFocus(bool isFocused) ; - - /** - * This event is fired when the target URL has changed. This is usually the result of - * hovering over a link on the page. - * - * @param url The updated target URL (or empty if the target URL is cleared). - */ - void onChangeTargetURL(const std::string& url) ; - - -public: - static TypeHandle get_class_type() { - return _type_handle; - } - static void init_type() { - TypedReferenceCount::init_type(); - register_type(_type_handle, "AwWebViewListener", - 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; -}; - -// #include "awWebViewListener.I" - -#endif diff --git a/panda/src/awesomium/awesomium_includes.h b/panda/src/awesomium/awesomium_includes.h deleted file mode 100644 index 27b8b9b9b0..0000000000 --- a/panda/src/awesomium/awesomium_includes.h +++ /dev/null @@ -1,21 +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 awesomium_includes.h - * @author rurbino - * @date 2010-10-08 - */ - -#ifndef _AWESOMIUM_INCLUDES_H_ -#define _AWESOMIUM_INCLUDES_H_ - -#include -#include -#include - -#endif diff --git a/panda/src/awesomium/config_awesomium.cxx b/panda/src/awesomium/config_awesomium.cxx deleted file mode 100644 index 35987d0e27..0000000000 --- a/panda/src/awesomium/config_awesomium.cxx +++ /dev/null @@ -1,51 +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 config_awesomium.cxx - * @author rurbino - * @date 2009-10-12 - */ - -#include "config_awesomium.h" -#include "awWebCore.h" -#include "awWebView.h" -#include "awWebViewListener.h" -#include "dconfig.h" - - -#if !defined(CPPPARSER) && !defined(LINK_ALL_STATIC) && !defined(BUILDING_PANDAAWESOMIUM) - #error Buildsystem error: BUILDING_PANDAAWESOMIUM not defined -#endif - -Configure(config_awesomium); -NotifyCategoryDef(awesomium, ""); - - -ConfigureFn(config_awesomium) { - init_libawesomium(); -} - -/** - * Initializes the library. This must be called at least once before any of - * the functions or classes in this library can be used. Normally it will be - * called by the static initializers and need not be called explicitly, but - * special cases exist. - */ -void -init_libawesomium() { - static bool initialized = false; - if (initialized) { - return; - } - initialized = true; - - AwWebCore::init_type(); - AwWebView::init_type(); - AwWebViewListener::init_type(); - -} diff --git a/panda/src/awesomium/config_awesomium.h b/panda/src/awesomium/config_awesomium.h deleted file mode 100644 index e02f047e16..0000000000 --- a/panda/src/awesomium/config_awesomium.h +++ /dev/null @@ -1,26 +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 config_awesomium.h - * @author rurbino - * @date 2009-10-12 - */ - -#ifndef CONFIG_AWESOMIUM_H -#define CONFIG_AWESOMIUM_H - -#include "pandabase.h" -#include "notifyCategoryProxy.h" - -#include "dconfig.h" - -NotifyCategoryDecl(awesomium, EXPCL_PANDAAWESOMIUM, EXPTP_PANDAAWESOMIUM); - -extern EXPCL_PANDAAWESOMIUM void init_libawesomium(); - -#endif /* CONFIG_AWESOMIUM_H */ diff --git a/panda/src/awesomium/pandaawesomium_composite1.cxx b/panda/src/awesomium/pandaawesomium_composite1.cxx deleted file mode 100644 index 1b0ef139ec..0000000000 --- a/panda/src/awesomium/pandaawesomium_composite1.cxx +++ /dev/null @@ -1,4 +0,0 @@ -#include "config_awesomium.cxx" -#include "awWebCore.cxx" -#include "awWebView.cxx" -#include "awWebViewListener.cxx" diff --git a/panda/src/pandabase/pandasymbols.h b/panda/src/pandabase/pandasymbols.h index ef83b4314e..3f529dab27 100644 --- a/panda/src/pandabase/pandasymbols.h +++ b/panda/src/pandabase/pandasymbols.h @@ -476,14 +476,6 @@ #define EXPTP_PANDA_WGLDISPLAY IMPORT_TEMPL #endif -#ifdef BUILDING_PANDAAWESOMIUM - #define EXPCL_PANDAAWESOMIUM EXPORT_CLASS - #define EXPTP_PANDAAWESOMIUM EXPORT_TEMPL -#else - #define EXPCL_PANDAAWESOMIUM IMPORT_CLASS - #define EXPTP_PANDAAWESOMIUM IMPORT_TEMPL -#endif - #ifdef BUILDING_PANDAGL #define EXPCL_PANDAGL EXPORT_CLASS #define EXPTP_PANDAGL EXPORT_TEMPL From b674104c1f59613ef23d6d41c9dd3ecbd0be347b Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 15:53:40 +0100 Subject: [PATCH 323/424] makepanda: don't error for harmless use of removed options Let's show a warning when an option has been removed but its presence would not have affected the build in an important way. For options like --use-awesomium we should still show an error since it invokes behavior we no longer support, but it should be harmless to specify --no-awesomium. --- makepanda/makepanda.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index ff6787f987..4ee16763a3 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -171,14 +171,22 @@ def parseopts(args): global COMPRESSOR,THREADCOUNT,OSXTARGET,OSX_ARCHS,HOST_URL global DEBVERSION,WHLVERSION,RPMRELEASE,GIT_COMMIT,P3DSUFFIX,RTDIST_VERSION global STRDXSDKVERSION, WINDOWS_SDK, MSVC_VERSION, BOOUSEINTELCOMPILER + + # Options for which to display a deprecation warning. + removedopts = [ + "use-touchinput", "no-touchinput", "no-awesomium", "no-directscripts", + ] + + # All recognized options. longopts = [ "help","distributor=","verbose","runtime","osxtarget=","tests", "optimize=","everything","nothing","installer","wheel","rtdist","nocolor", "version=","lzma","no-python","threads=","outputdir=","override=", "static","host=","debversion=","rpmrelease=","p3dsuffix=","rtdist-version=", "directx-sdk=", "windows-sdk=", "msvc-version=", "clean", "use-icl", - "universal", "target=", "arch=", "git-commit=", "no-directscripts", - "use-touchinput", "no-touchinput"] + "universal", "target=", "arch=", "git-commit=", + ] + removedopts + anything = 0 optimize = "" target = None @@ -190,6 +198,7 @@ def parseopts(args): longopts.append("no-" + pkg.lower()) longopts.append(pkg.lower() + "-incdir=") longopts.append(pkg.lower() + "-libdir=") + try: opts, extras = getopt.getopt(args, "", longopts) for option, value in opts: @@ -230,7 +239,6 @@ def parseopts(args): # Backward compatibility, OPENGL was renamed to GL elif (option=="--use-opengl"): PkgEnable("GL") elif (option=="--no-opengl"): PkgDisable("GL") - elif (option=="--no-directscripts"): pass elif (option=="--directx-sdk"): STRDXSDKVERSION = value.strip().lower() if STRDXSDKVERSION == '': @@ -242,6 +250,8 @@ def parseopts(args): MSVC_VERSION = value.strip().lower() elif (option=="--use-icl"): BOOUSEINTELCOMPILER = True elif (option=="--clean"): clean_build = True + elif (option[2:] in removedopts): + Warn("Ignoring removed option %s" % (option)) else: for pkg in PkgListGet(): if option == "--use-" + pkg.lower(): From b474ef6b1e0ce3e268008a631f9a666e1ae20e98 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 17:01:35 +0100 Subject: [PATCH 324/424] makepanda: split out installer code into separate makepackage.py --- makepanda/makepackage.py | 964 +++++++++++++++++++++++++++++++++++++++ makepanda/makepanda.py | 896 +----------------------------------- 2 files changed, 984 insertions(+), 876 deletions(-) create mode 100755 makepanda/makepackage.py diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py new file mode 100755 index 0000000000..dc3da45e19 --- /dev/null +++ b/makepanda/makepackage.py @@ -0,0 +1,964 @@ +#!/usr/bin/env python + +from makepandacore import * +from installpanda import * +import sys +import os +import shutil + + +INSTALLER_DEB_FILE = """ +Package: panda3dMAJOR +Version: VERSION +Section: libdevel +Priority: optional +Architecture: ARCH +Essential: no +Depends: DEPENDS +Recommends: RECOMMENDS +Provides: panda3d, pythonPV-panda3d +Conflicts: panda3d, pythonPV-panda3d +Replaces: panda3d, pythonPV-panda3d +Maintainer: rdb +Installed-Size: INSTSIZE +Description: Panda3D free 3D engine SDK + Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost. + Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface. + . + This package contains the SDK for development with Panda3D. + +""" + +RUNTIME_INSTALLER_DEB_FILE = """ +Package: panda3d-runtime +Version: VERSION +Section: web +Priority: optional +Architecture: ARCH +Essential: no +Depends: DEPENDS +Provides: panda3d-runtime +Maintainer: rdb +Installed-Size: INSTSIZE +Description: Runtime binary and browser plugin for the Panda3D Game Engine + This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file. + +""" + + +# We're not putting "python" in the "Requires" field, +# since the rpm-based distros don't have a common +# naming for the Python package. +INSTALLER_SPEC_FILE = """ +Summary: The Panda3D free 3D engine SDK +Name: panda3d +Version: VERSION +Release: RPMRELEASE +License: BSD License +Group: Development/Libraries +BuildRoot: PANDASOURCE/targetroot +%description +Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost. +Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface. + +This package contains the SDK for development with Panda3D, install panda3d-runtime for the runtime files. +%post +/sbin/ldconfig +%postun +/sbin/ldconfig +%files +%defattr(-,root,root) +/etc/Confauto.prc +/etc/Config.prc +/usr/share/panda3d +/etc/ld.so.conf.d/panda3d.conf +/usr/%_lib/panda3d +""" + PYTHON_SITEPACKAGES + """ +/usr/include/panda3d +""" +INSTALLER_SPEC_FILE_PVIEW = \ +"""/usr/share/applications/pview.desktop +/usr/share/mime-info/panda3d.mime +/usr/share/mime-info/panda3d.keys +/usr/share/mime/packages/panda3d.xml +/usr/share/application-registry/panda3d.applications +""" + +RUNTIME_INSTALLER_SPEC_FILE = """ +Summary: Runtime binary and browser plugin for the Panda3D Game Engine +Name: panda3d-runtime +Version: VERSION +Release: RPMRELEASE +License: BSD License +Group: Productivity/Graphics/Other +BuildRoot: PANDASOURCE/targetroot +%description +This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file. +%files +%defattr(-,root,root) +/usr/bin/panda3d +/usr/%_lib/nppanda3d.so +/usr/%_lib/mozilla/plugins/nppanda3d.so +/usr/%_lib/mozilla-firefox/plugins/nppanda3d.so +/usr/%_lib/xulrunner-addons/plugins/nppanda3d.so +/usr/share/mime-info/panda3d-runtime.mime +/usr/share/mime-info/panda3d-runtime.keys +/usr/share/mime/packages/panda3d-runtime.xml +/usr/share/application-registry/panda3d-runtime.applications +/usr/share/applications/*.desktop +""" + +# plist file for Mac OSX +Info_plist = """ + + + + CFBundleIdentifier + {package_id} + CFBundleShortVersionString + {version} + IFPkgFlagRelocatable + + IFPkgFlagAuthorizationAction + RootAuthorization + IFPkgFlagAllowBackRev + + + +""" + +# FreeBSD pkg-descr +INSTALLER_PKG_DESCR_FILE = """ +Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost. +Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface. + +This package contains the SDK for development with Panda3D, install panda3d-runtime for the runtime files. + +WWW: https://www.panda3d.org/ +""" + +# FreeBSD pkg-descr +RUNTIME_INSTALLER_PKG_DESCR_FILE = """ +Runtime binary and browser plugin for the Panda3D Game Engine + +This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file. + +WWW: https://www.panda3d.org/ +""" + +# FreeBSD PKG Manifest template file +INSTALLER_PKG_MANIFEST_FILE = """ +name: NAME +version: VERSION +arch: ARCH +origin: ORIGIN +comment: "Panda3D free 3D engine SDK" +www: https://www.panda3d.org +maintainer: rdb +prefix: /usr/local +flatsize: INSTSIZEMB +deps: {DEPENDS} +""" + + +def MakeInstallerNSIS(version, file, title, installdir, runtime=False, compressor="lzma", **kwargs): + outputdir = GetOutputDir() + + if os.path.isfile(file): + os.remove(file) + elif os.path.isdir(file): + shutil.rmtree(file) + + if "PYTHONVERSION" in SDK: + pyver = SDK["PYTHONVERSION"][6:9] + else: + pyver = "%d.%d" % (sys.version_info[:2]) + + if GetTargetArch() == 'x64': + regview = '64' + else: + regview = '32' + if int(pyver[0]) == 3 and int(pyver[2]) >= 5: + pyver += '-32' + + if runtime: + # Invoke the make_installer script. + AddToPathEnv("PATH", outputdir + "\\bin") + AddToPathEnv("PATH", outputdir + "\\plugins") + + cmd = sys.executable + " -B -u " + os.path.join("direct", "src", "plugin_installer", "make_installer.py") + cmd += " --version %s --regview %s" % (version, regview) + + if GetTargetArch() == 'x64': + cmd += " --install \"$PROGRAMFILES64\\Panda3D\" " + else: + cmd += " --install \"$PROGRAMFILES32\\Panda3D\" " + + oscmd(cmd) + shutil.move(os.path.join("direct", "src", "plugin_installer", "p3d-setup.exe"), file) + return + + print("Building "+title+" installer at %s" % (file)) + if compressor != "lzma": + print("Note: you are using zlib, which is faster, but lzma gives better compression.") + if os.path.exists("nsis-output.exe"): + os.remove("nsis-output.exe") + WriteFile(outputdir+"/tmp/__init__.py", "") + + nsis_defs = { + 'COMPRESSOR': compressor, + 'TITLE' : title, + 'INSTALLDIR': installdir, + 'OUTFILE' : '..\\' + file, + 'BUILT' : '..\\' + outputdir, + 'SOURCE' : '..', + 'PYVER' : pyver, + 'REGVIEW' : regview, + 'EXT_SUFFIX': GetExtensionSuffix(), + } + + if GetHost() == 'windows': + cmd = os.path.join(GetThirdpartyBase(), 'win-nsis', 'makensis') + ' /V2' + for item in nsis_defs.items(): + cmd += ' /D%s="%s"' % item + else: + cmd = 'makensis -V2' + for item in nsis_defs.items(): + cmd += ' -D%s="%s"' % item + + cmd += ' "makepanda\\installer.nsi"' + oscmd(cmd) + + +def MakeDebugSymbolArchive(zipname, dirname): + outputdir = GetOutputDir() + + import zipfile + zip = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED) + + for fn in glob.glob(os.path.join(outputdir, 'bin', '*.pdb')): + zip.write(fn, dirname + '/bin/' + os.path.basename(fn)) + + for fn in glob.glob(os.path.join(outputdir, 'panda3d', '*.pdb')): + zip.write(fn, dirname + '/panda3d/' + os.path.basename(fn)) + + for fn in glob.glob(os.path.join(outputdir, 'plugins', '*.pdb')): + zip.write(fn, dirname + '/plugins/' + os.path.basename(fn)) + + for fn in glob.glob(os.path.join(outputdir, 'python', '*.pdb')): + zip.write(fn, dirname + '/python/' + os.path.basename(fn)) + + for fn in glob.glob(os.path.join(outputdir, 'python', 'DLLs', '*.pdb')): + zip.write(fn, dirname + '/python/DLLs/' + os.path.basename(fn)) + + zip.close() + + +def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs): + outputdir = GetOutputDir() + + if not runtime and not PkgSkip("PYTHON"): + if "PYTHONVERSION" in SDK: + PYTHONV = SDK["PYTHONVERSION"].rstrip('dmu') + else: + PYTHONV = "python%d.%d" % (sys.version_info[:2]) + else: + PYTHONV = "python" + PV = PYTHONV.replace("python", "") + + major_version = '.'.join(version.split('.')[:2]) + if not debversion: + debversion = version + + # Clean and set up a directory to install Panda3D into + oscmd("rm -rf targetroot data.tar.gz control.tar.gz panda3d.spec") + oscmd("mkdir --mode=0755 targetroot") + + dpkg_present = False + if os.path.exists("/usr/bin/dpkg-architecture") and os.path.exists("/usr/bin/dpkg-deb"): + dpkg_present = True + rpmbuild_present = False + if os.path.exists("/usr/bin/rpmbuild"): + rpmbuild_present = True + + if dpkg_present and rpmbuild_present: + Warn("both dpkg and rpmbuild present.") + + if dpkg_present: + # Invoke installpanda.py to install it into a temporary dir + lib_dir = GetDebLibDir() + if runtime: + InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=lib_dir) + else: + InstallPanda(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=lib_dir) + oscmd("chmod -R 755 targetroot/usr/share/panda3d") + oscmd("mkdir -m 0755 -p targetroot/usr/share/man/man1") + oscmd("install -m 0644 doc/man/*.1 targetroot/usr/share/man/man1/") + + oscmd("dpkg --print-architecture > "+outputdir+"/tmp/architecture.txt") + pkg_arch = ReadFile(outputdir+"/tmp/architecture.txt").strip() + if runtime: + txt = RUNTIME_INSTALLER_DEB_FILE[1:] + else: + txt = INSTALLER_DEB_FILE[1:] + txt = txt.replace("VERSION", debversion).replace("ARCH", pkg_arch).replace("PV", PV).replace("MAJOR", major_version) + txt = txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024)) + oscmd("mkdir --mode=0755 -p targetroot/DEBIAN") + oscmd("cd targetroot && (find usr -type f -exec md5sum {} ;) > DEBIAN/md5sums") + if not runtime: + oscmd("cd targetroot && (find etc -type f -exec md5sum {} ;) >> DEBIAN/md5sums") + WriteFile("targetroot/DEBIAN/conffiles","/etc/Config.prc\n") + WriteFile("targetroot/DEBIAN/postinst","#!/bin/sh\necho running ldconfig\nldconfig\n") + oscmd("cp targetroot/DEBIAN/postinst targetroot/DEBIAN/postrm") + + # Determine the package name and the locations that + # dpkg-shlibdeps should look in for executables. + pkg_version = debversion + if runtime: + pkg_name = "panda3d-runtime" + lib_pattern = "debian/%s/usr/%s/*.so" % (pkg_name, lib_dir) + else: + pkg_name = "panda3d" + major_version + lib_pattern = "debian/%s/usr/%s/panda3d/*.so*" % (pkg_name, lib_dir) + bin_pattern = "debian/%s/usr/bin/*" % (pkg_name) + + # dpkg-shlibdeps looks in the debian/{pkg_name}/DEBIAN/shlibs directory + # and also expects a debian/control file, so we create this dummy set-up. + oscmd("mkdir targetroot/debian") + oscmd("ln -s .. targetroot/debian/" + pkg_name) + WriteFile("targetroot/debian/control", "") + + dpkg_shlibdeps = "dpkg-shlibdeps" + if GetVerbose(): + dpkg_shlibdeps += " -v" + + if runtime: + # The runtime doesn't export any useful symbols, so just query the dependencies. + oscmd("cd targetroot && %(dpkg_shlibdeps)s -x%(pkg_name)s %(lib_pattern)s %(bin_pattern)s*" % locals()) + depends = ReadFile("targetroot/debian/substvars").replace("shlibs:Depends=", "").strip() + recommends = "" + else: + pkg_name = "panda3d" + major_version + pkg_dir = "debian/panda3d" + major_version + + # Generate a symbols file so that other packages can know which symbols we export. + oscmd("cd targetroot && dpkg-gensymbols -q -ODEBIAN/symbols -v%(pkg_version)s -p%(pkg_name)s -e%(lib_pattern)s" % locals()) + + # Library dependencies are required, binary dependencies are recommended. + # We explicitly exclude libphysx-extras since we don't want to depend on PhysX. + oscmd("cd targetroot && LD_LIBRARY_PATH=usr/%(lib_dir)s/panda3d %(dpkg_shlibdeps)s -Tdebian/substvars_dep --ignore-missing-info -x%(pkg_name)s -xlibphysx-extras %(lib_pattern)s" % locals()) + oscmd("cd targetroot && LD_LIBRARY_PATH=usr/%(lib_dir)s/panda3d %(dpkg_shlibdeps)s -Tdebian/substvars_rec --ignore-missing-info -x%(pkg_name)s %(bin_pattern)s" % locals()) + + # Parse the substvars files generated by dpkg-shlibdeps. + depends = ReadFile("targetroot/debian/substvars_dep").replace("shlibs:Depends=", "").strip() + recommends = ReadFile("targetroot/debian/substvars_rec").replace("shlibs:Depends=", "").strip() + if PkgSkip("PYTHON")==0: + depends += ", " + PYTHONV + recommends += ", python-wxversion, python-profiler (>= " + PV + "), python-pmw, python-tk (>= " + PV + ")" + if PkgSkip("NVIDIACG")==0: + depends += ", nvidia-cg-toolkit" + + # Write back the dependencies, and delete the dummy set-up. + txt = txt.replace("DEPENDS", depends.strip(', ')) + txt = txt.replace("RECOMMENDS", recommends.strip(', ')) + WriteFile("targetroot/DEBIAN/control", txt) + oscmd("rm -rf targetroot/debian") + + # Package it all up into a .deb file. + oscmd("chmod -R 755 targetroot/DEBIAN") + oscmd("chmod 644 targetroot/DEBIAN/control targetroot/DEBIAN/md5sums") + if not runtime: + oscmd("chmod 644 targetroot/DEBIAN/conffiles targetroot/DEBIAN/symbols") + oscmd("fakeroot dpkg-deb -b targetroot %s_%s_%s.deb" % (pkg_name, pkg_version, pkg_arch)) + + elif rpmbuild_present: + # Invoke installpanda.py to install it into a temporary dir + if runtime: + InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=GetRPMLibDir()) + else: + InstallPanda(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=GetRPMLibDir()) + oscmd("chmod -R 755 targetroot/usr/share/panda3d") + + oscmd("rpm -E '%_target_cpu' > "+outputdir+"/tmp/architecture.txt") + arch = ReadFile(outputdir+"/tmp/architecture.txt").strip() + pandasource = os.path.abspath(os.getcwd()) + + if runtime: + txt = RUNTIME_INSTALLER_SPEC_FILE[1:] + else: + txt = INSTALLER_SPEC_FILE[1:] + + # Add the MIME associations if we have pview + if not PkgSkip("PVIEW"): + txt += INSTALLER_SPEC_FILE_PVIEW + + # Add the binaries in /usr/bin explicitly to the spec file + for base in os.listdir(outputdir + "/bin"): + txt += "/usr/bin/%s\n" % (base) + + # Write out the spec file. + txt = txt.replace("VERSION", version) + txt = txt.replace("RPMRELEASE", rpmrelease) + txt = txt.replace("PANDASOURCE", pandasource) + txt = txt.replace("PV", PV) + WriteFile("panda3d.spec", txt) + + oscmd("fakeroot rpmbuild --define '_rpmdir "+pandasource+"' --buildroot '"+os.path.abspath("targetroot")+"' -bb panda3d.spec") + if runtime: + oscmd("mv "+arch+"/panda3d-runtime-"+version+"-"+rpmrelease+"."+arch+".rpm .") + else: + oscmd("mv "+arch+"/panda3d-"+version+"-"+rpmrelease+"."+arch+".rpm .") + oscmd("rm -rf "+arch, True) + + else: + exit("To build an installer, either rpmbuild or dpkg-deb must be present on your system!") + + +def MakeInstallerOSX(version, runtime=False, **kwargs): + outputdir = GetOutputDir() + + if runtime: + # Invoke the make_installer script. + AddToPathEnv("DYLD_LIBRARY_PATH", outputdir + "/plugins") + cmdstr = sys.executable + " " + if sys.version_info >= (2, 6): + cmdstr += "-B " + + cmdstr += "direct/src/plugin_installer/make_installer.py --version %s" % version + oscmd(cmdstr) + return + + if "PYTHONVERSION" in SDK: + pyver = SDK["PYTHONVERSION"][6:9] + else: + pyver = "%d.%d" % (sys.version_info[:2]) + + dmg_name = "Panda3D-" + version + if not pyver.startswith("2."): + dmg_name += "-py" + pyver + dmg_name += ".dmg" + + import compileall + if (os.path.isfile(dmg_name)): oscmd("rm -f %s" % dmg_name) + if (os.path.exists("dstroot")): oscmd("rm -rf dstroot") + if (os.path.exists("Panda3D-rw.dmg")): oscmd('rm -f Panda3D-rw.dmg') + + oscmd("mkdir -p dstroot/base/Developer/Panda3D/lib") + oscmd("mkdir -p dstroot/base/Developer/Panda3D/etc") + oscmd("cp %s/etc/Config.prc dstroot/base/Developer/Panda3D/etc/Config.prc" % outputdir) + oscmd("cp %s/etc/Confauto.prc dstroot/base/Developer/Panda3D/etc/Confauto.prc" % outputdir) + oscmd("cp -R %s/models dstroot/base/Developer/Panda3D/models" % outputdir) + oscmd("cp -R doc/LICENSE dstroot/base/Developer/Panda3D/LICENSE") + oscmd("cp -R doc/ReleaseNotes dstroot/base/Developer/Panda3D/ReleaseNotes") + oscmd("cp -R %s/Frameworks dstroot/base/Developer/Panda3D/Frameworks" % outputdir) + if os.path.isdir(outputdir+"/plugins"): + oscmd("cp -R %s/plugins dstroot/base/Developer/Panda3D/plugins" % outputdir) + + # Libraries that shouldn't be in base, but are instead in other modules. + no_base_libs = ['libp3ffmpeg', 'libp3fmod_audio', 'libfmodex', 'libfmodexL'] + + for base in os.listdir(outputdir+"/lib"): + if not base.endswith(".a") and base.split('.')[0] not in no_base_libs: + libname = "dstroot/base/Developer/Panda3D/lib/" + base + # We really need to specify -R in order not to follow symlinks + # On OSX, just specifying -P is not enough to do that. + oscmd("cp -R -P " + outputdir + "/lib/" + base + " " + libname) + + oscmd("mkdir -p dstroot/tools/Developer/Panda3D/bin") + oscmd("mkdir -p dstroot/tools/Developer/Tools") + oscmd("ln -s ../Panda3D/bin dstroot/tools/Developer/Tools/Panda3D") + oscmd("mkdir -p dstroot/tools/etc/paths.d") + # Trailing newline is important, works around a bug in OSX + WriteFile("dstroot/tools/etc/paths.d/Panda3D", "/Developer/Panda3D/bin\n") + + oscmd("mkdir -m 0755 -p dstroot/tools/usr/local/share/man/man1") + oscmd("install -m 0644 doc/man/*.1 dstroot/tools/usr/local/share/man/man1/") + + for base in os.listdir(outputdir+"/bin"): + binname = "dstroot/tools/Developer/Panda3D/bin/" + base + # OSX needs the -R argument to copy symbolic links correctly, it doesn't have -d. How weird. + oscmd("cp -R " + outputdir + "/bin/" + base + " " + binname) + + if not PkgSkip("PYTHON"): + pyexec = SDK.get("PYTHONEXEC", sys.executable) + oscmd("mkdir -p dstroot/pythoncode/usr/local/bin") + oscmd("mkdir -p dstroot/pythoncode/Developer/Panda3D/panda3d") + oscmd("mkdir -p dstroot/pythoncode/Library/Python/%s/site-packages" % pyver) + WriteFile("dstroot/pythoncode/Library/Python/%s/site-packages/Panda3D.pth" % pyver, "/Developer/Panda3D") + oscmd("cp -R %s/pandac dstroot/pythoncode/Developer/Panda3D/pandac" % outputdir) + oscmd("cp -R %s/direct dstroot/pythoncode/Developer/Panda3D/direct" % outputdir) + oscmd("ln -s %s dstroot/pythoncode/usr/local/bin/ppython" % pyexec) + oscmd("cp -R %s/*.so dstroot/pythoncode/Developer/Panda3D/" % outputdir, True) + oscmd("cp -R %s/*.py dstroot/pythoncode/Developer/Panda3D/" % outputdir, True) + if os.path.isdir(outputdir+"/Pmw"): + oscmd("cp -R %s/Pmw dstroot/pythoncode/Developer/Panda3D/Pmw" % outputdir) + compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/Pmw") + WriteFile("dstroot/pythoncode/Developer/Panda3D/direct/__init__.py", "") + for base in os.listdir("dstroot/pythoncode/Developer/Panda3D/direct"): + if ((base != "extensions") and (base != "extensions_native")): + compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/direct/"+base) + + suffix = GetExtensionSuffix() + for base in os.listdir(outputdir+"/panda3d"): + if base.endswith('.py') or (base.endswith(suffix) and '.' not in base[:-len(suffix)]): + libname = "dstroot/pythoncode/Developer/Panda3D/panda3d/" + base + # We really need to specify -R in order not to follow symlinks + # On OSX, just specifying -P is not enough to do that. + oscmd("cp -R -P " + outputdir + "/panda3d/" + base + " " + libname) + + if not PkgSkip("FFMPEG"): + oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib") + oscmd("cp -R %s/lib/libp3ffmpeg.* dstroot/ffmpeg/Developer/Panda3D/lib/" % outputdir) + + #if not PkgSkip("OPENAL"): + # oscmd("mkdir -p dstroot/openal/Developer/Panda3D/lib") + # oscmd("cp -R %s/lib/libp3openal_audio.* dstroot/openal/Developer/Panda3D/lib/" % outputdir) + + if not PkgSkip("FMODEX"): + oscmd("mkdir -p dstroot/fmodex/Developer/Panda3D/lib") + oscmd("cp -R %s/lib/libp3fmod_audio.* dstroot/fmodex/Developer/Panda3D/lib/" % outputdir) + oscmd("cp -R %s/lib/libfmodex* dstroot/fmodex/Developer/Panda3D/lib/" % outputdir) + + oscmd("mkdir -p dstroot/headers/Developer/Panda3D/lib") + oscmd("cp -R %s/include dstroot/headers/Developer/Panda3D/include" % outputdir) + if os.path.isfile(outputdir + "/lib/libp3pystub.a"): + oscmd("cp -R -P %s/lib/libp3pystub.a dstroot/headers/Developer/Panda3D/lib/" % outputdir) + + if os.path.isdir("samples"): + oscmd("mkdir -p dstroot/samples/Developer/Examples/Panda3D") + oscmd("cp -R samples/* dstroot/samples/Developer/Examples/Panda3D/") + + oscmd("chmod -R 0775 dstroot/*") + DeleteVCS("dstroot") + DeleteBuildFiles("dstroot") + # We need to be root to perform a chown. Bleh. + # Fortunately PackageMaker does it for us, on 10.5 and above. + #oscmd("chown -R root:admin dstroot/*", True) + + oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/") + oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Resources/en.lproj/") + + pkgs = ["base", "tools", "headers"] + if not PkgSkip("PYTHON"): pkgs.append("pythoncode") + if not PkgSkip("FFMPEG"): pkgs.append("ffmpeg") + #if not PkgSkip("OPENAL"): pkgs.append("openal") + if not PkgSkip("FMODEX"): pkgs.append("fmodex") + if os.path.isdir("samples"): pkgs.append("samples") + for pkg in pkgs: + identifier = "org.panda3d.panda3d.%s.pkg" % pkg + plist = open("/tmp/Info_plist", "w") + plist.write(Info_plist.format(package_id=identifier, version=version)) + plist.close() + if not os.path.isdir("dstroot/" + pkg): + os.makedirs("dstroot/" + pkg) + + if os.path.exists("/usr/bin/pkgbuild"): + # This new package builder is used in Lion and above. + cmd = '/usr/bin/pkgbuild --identifier ' + identifier + ' --version ' + version + ' --root dstroot/' + pkg + '/ dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg' + + # In older versions, we use PackageMaker. Apple keeps changing its location. + elif os.path.exists("/Developer/usr/bin/packagemaker"): + cmd = '/Developer/usr/bin/packagemaker --info /tmp/Info_plist --version ' + version + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate' + elif os.path.exists("/Applications/Xcode.app/Contents/Applications/PackageMaker.app/Contents/MacOS/PackageMaker"): + cmd = '/Applications/Xcode.app/Contents/Applications/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + version + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate' + elif os.path.exists("/Developer/Tools/PackageMaker.app/Contents/MacOS/PackageMaker"): + cmd = '/Developer/Tools/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + version + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate' + elif os.path.exists("/Developer/Tools/packagemaker"): + cmd = '/Developer/Tools/packagemaker -build -f dstroot/' + pkg + '/ -p dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg -i /tmp/Info_plist' + elif os.path.exists("/Applications/PackageMaker.app/Contents/MacOS/PackageMaker"): + cmd = '/Applications/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + version + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate' + else: + exit("Neither pkgbuild nor PackageMaker could be found!") + oscmd(cmd) + + if os.path.isfile("/tmp/Info_plist"): + oscmd("rm -f /tmp/Info_plist") + + # Now that we've built all of the individual packages, build the metapackage. + dist = open("dstroot/Panda3D/Panda3D.mpkg/Contents/distribution.dist", "w") + dist.write('\n') + dist.write('\n') + dist.write(' Panda3D SDK %s\n' % (version)) + dist.write(' \n') + dist.write(' %s\n' % ReadFile("doc/LICENSE")) + dist.write(' \n') + for pkg in pkgs: + dist.write(' \n' % (pkg)) + dist.write(' \n') + dist.write(' \n') + dist.write(' \n') + dist.write(' \n') + dist.write(' \n') + dist.write(' \n') + dist.write(' \n') + + if not PkgSkip("PYTHON"): + dist.write(' \n') + dist.write(' \n') + dist.write(' \n') + + if not PkgSkip("FFMPEG"): + dist.write(' \n') + elif PkgSkip("VORBIS"): + dist.write(' It is not required for loading .wav or .opus files, which Panda3D can read out of the box.">\n') + elif PkgSkip("OPUS"): + dist.write(' It is not required for loading .wav or .ogg files, which Panda3D can read out of the box.">\n') + else: + dist.write(' It is not required for loading .wav, .ogg or .opus files, which Panda3D can read out of the box.">\n') + dist.write(' \n') + dist.write(' \n') + + #if not PkgSkip("OPENAL"): + # dist.write(' \n') + # dist.write(' \n') + # dist.write(' \n') + + if not PkgSkip("FMODEX"): + dist.write(' \n') + dist.write(' \n') + dist.write(' \n') + + if os.path.isdir("samples"): + dist.write(' \n') + dist.write(' \n') + dist.write(' \n') + + dist.write(' \n') + dist.write(' \n') + dist.write(' \n') + for pkg in pkgs: + size = GetDirectorySize("dstroot/" + pkg) // 1024 + dist.write(' file:./Contents/Packages/%s.pkg\n' % (pkg, size, pkg)) + dist.write('\n') + dist.close() + + oscmd('hdiutil create Panda3D-rw.dmg -volname "Panda3D SDK %s" -srcfolder dstroot/Panda3D' % (version)) + oscmd('hdiutil convert Panda3D-rw.dmg -format UDBZ -o %s' % (dmg_name)) + oscmd('rm -f Panda3D-rw.dmg') + + +def MakeInstallerFreeBSD(version, runtime=False, **kwargs): + outputdir = GetOutputDir() + + oscmd("rm -rf targetroot +DESC pkg-plist +MANIFEST") + oscmd("mkdir targetroot") + + # Invoke installpanda.py to install it into a temporary dir + if runtime: + InstallRuntime(destdir="targetroot", prefix="/usr/local", outputdir=outputdir) + else: + InstallPanda(destdir="targetroot", prefix="/usr/local", outputdir=outputdir) + + if not os.path.exists("/usr/sbin/pkg"): + exit("Cannot create an installer without pkg") + + plist_txt = '' + for root, dirs, files in os.walk("targetroot/usr/local/", True): + for f in files: + plist_txt += os.path.join(root, f)[21:] + "\n" + + if not runtime: + plist_txt += "@postexec /sbin/ldconfig -m /usr/local/lib/panda3d\n" + plist_txt += "@postunexec /sbin/ldconfig -R\n" + + for remdir in ("lib/panda3d", "share/panda3d", "include/panda3d"): + for root, dirs, files in os.walk("targetroot/usr/local/" + remdir, False): + for d in dirs: + plist_txt += "@dir %s\n" % os.path.join(root, d)[21:] + plist_txt += "@dir %s\n" % remdir + + oscmd("echo \"`pkg config abi | tr '[:upper:]' '[:lower:]' | cut -d: -f1,2`:*\" > " + outputdir + "/tmp/architecture.txt") + pkg_arch = ReadFile(outputdir+"/tmp/architecture.txt").strip() + + dependencies = '' + if not PkgSkip("PYTHON"): + # If this version of Python was installed from a package or ports, let's mark it as dependency. + oscmd("rm -f %s/tmp/python_dep" % outputdir) + + if "PYTHONVERSION" in SDK: + pyver_nodot = SDK["PYTHONVERSION"][6:9:2] + else: + pyver_nodot = "%d%d" % (sys.version_info[:2]) + + oscmd("pkg query \"\n\t%%n : {\n\t\torigin : %%o,\n\t\tversion : %%v\n\t},\n\" python%s > %s/tmp/python_dep" % (pyver_nodot, outputdir), True) + if os.path.isfile(outputdir + "/tmp/python_dep"): + python_pkg = ReadFile(outputdir + "/tmp/python_dep") + if python_pkg: + dependencies += python_pkg + + manifest_txt = INSTALLER_PKG_MANIFEST_FILE[1:].replace("NAME", 'panda3d' if not runtime else 'panda3d-runtime') + manifest_txt = manifest_txt.replace("VERSION", version) + manifest_txt = manifest_txt.replace("ARCH", pkg_arch) + manifest_txt = manifest_txt.replace("ORIGIN", 'devel/panda3d' if not runtime else 'graphics/panda3d-runtime') + manifest_txt = manifest_txt.replace("DEPENDS", dependencies) + manifest_txt = manifest_txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024 / 1024)) + + WriteFile("pkg-plist", plist_txt) + WriteFile("+DESC", INSTALLER_PKG_DESCR_FILE[1:] if not runtime else RUNTIME_INSTALLER_PKG_DESCR_FILE[1:]) + WriteFile("+MANIFEST", manifest_txt) + oscmd("pkg create -p pkg-plist -r %s -m . -o . %s" % (os.path.abspath("targetroot"), "--verbose" if GetVerbose() else "--quiet")) + + +def MakeInstallerAndroid(version, **kwargs): + outputdir = GetOutputDir() + oscmd("rm -rf apkroot") + oscmd("mkdir apkroot") + + # Also remove the temporary apks. + apk_unaligned = os.path.join(outputdir, "tmp", "panda3d-unaligned.apk") + apk_unsigned = os.path.join(outputdir, "tmp", "panda3d-unsigned.apk") + if os.path.exists(apk_unaligned): + os.unlink(apk_unaligned) + if os.path.exists(apk_unsigned): + os.unlink(apk_unsigned) + + # Compile the Java classes into a Dalvik executable. + dx_cmd = "dx --dex --output=apkroot/classes.dex " + if GetOptimize() <= 2: + dx_cmd += "--debug " + if GetVerbose(): + dx_cmd += "--verbose " + if "ANDROID_API" in SDK: + dx_cmd += "--min-sdk-version=%d " % (SDK["ANDROID_API"]) + dx_cmd += os.path.join(outputdir, "classes") + oscmd(dx_cmd) + + # Copy the libraries one by one. In case of library dependencies, strip + # off any suffix (eg. libfile.so.1.0), as Android does not support them. + source_dir = os.path.join(outputdir, "lib") + target_dir = os.path.join("apkroot", "lib", SDK["ANDROID_ABI"]) + oscmd("mkdir -p %s" % (target_dir)) + + # Determine the library directories we should look in. + libpath = [source_dir] + for dir in os.environ.get("LD_LIBRARY_PATH", "").split(':'): + dir = os.path.expandvars(dir) + dir = os.path.expanduser(dir) + if os.path.isdir(dir): + dir = os.path.realpath(dir) + if not dir.startswith("/system") and not dir.startswith("/vendor"): + libpath.append(dir) + + def copy_library(source, base): + # Copy file to destination, stripping version suffix. + target = os.path.join(target_dir, base) + if not target.endswith('.so'): + target = target.rpartition('.so.')[0] + '.so' + + if os.path.isfile(target): + # Already processed. + return + + oscmd("cp %s %s" % (source, target)) + + # Walk through the library dependencies. + oscmd("ldd %s | grep .so > %s/tmp/otool-libs.txt" % (target, outputdir), True) + for line in open(outputdir + "/tmp/otool-libs.txt", "r"): + line = line.strip() + if not line: + continue + if '.so.' in line: + dep = line.rpartition('.so.')[0] + '.so' + oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target), True) + else: + dep = line + + # Find it on the LD_LIBRARY_PATH. + for dir in libpath: + fulldep = os.path.join(dir, dep) + if os.path.isfile(fulldep): + copy_library(os.path.realpath(fulldep), dep) + break + + # Now copy every lib in the lib dir, and its dependencies. + for base in os.listdir(source_dir): + if not base.startswith('lib'): + continue + if not base.endswith('.so') and '.so.' not in base: + continue + + source = os.path.join(source_dir, base) + if os.path.islink(source): + continue + copy_library(source, base) + + # Same for Python extension modules. However, Android is strict about + # library naming, so we have a special naming scheme for these, in + # conjunction with a custom import hook to find these modules. + if not PkgSkip("PYTHON"): + suffix = GetExtensionSuffix() + source_dir = os.path.join(outputdir, "panda3d") + for base in os.listdir(source_dir): + if not base.endswith(suffix): + continue + modname = base[:-len(suffix)] + if '.' not in modname: + source = os.path.join(source_dir, base) + copy_library(source, "libpy.panda3d.{}.so".format(modname)) + + # Same for standard Python modules. + import _ctypes + source_dir = os.path.dirname(_ctypes.__file__) + for base in os.listdir(source_dir): + if not base.endswith('.so'): + continue + modname = base.partition('.')[0] + source = os.path.join(source_dir, base) + copy_library(source, "libpy.{}.so".format(modname)) + + def copy_python_tree(source_root, target_root): + for source_dir, dirs, files in os.walk(source_root): + if 'site-packages' in dirs: + dirs.remove('site-packages') + + if not any(base.endswith('.py') for base in files): + continue + + target_dir = os.path.join(target_root, os.path.relpath(source_dir, source_root)) + target_dir = os.path.normpath(target_dir) + os.makedirs(target_dir, 0o755) + + for base in files: + if base.endswith('.py'): + target = os.path.join(target_dir, base) + shutil.copy(os.path.join(source_dir, base), target) + + # Copy the Python standard library to the .apk as well. + from distutils.sysconfig import get_python_lib + stdlib_source = get_python_lib(False, True) + stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info)) + copy_python_tree(stdlib_source, stdlib_target) + + # But also copy over our custom site.py. + shutil.copy("panda/src/android/site.py", os.path.join(stdlib_target, "site.py")) + + # And now make a site-packages directory containing our direct/panda3d/pandac modules. + for tree in "panda3d", "direct", "pandac": + copy_python_tree(os.path.join(outputdir, tree), os.path.join(stdlib_target, "site-packages", tree)) + + # Copy the models and config files to the virtual assets filesystem. + oscmd("mkdir apkroot/assets") + oscmd("cp -R %s apkroot/assets/models" % (os.path.join(outputdir, "models"))) + oscmd("cp -R %s apkroot/assets/etc" % (os.path.join(outputdir, "etc"))) + + # Make an empty res folder. It's needed for the apk to be installable, apparently. + oscmd("mkdir apkroot/res") + + # Now package up the application + oscmd("cp panda/src/android/pview_manifest.xml apkroot/AndroidManifest.xml") + aapt_cmd = "aapt package" + aapt_cmd += " -F %s" % (apk_unaligned) + aapt_cmd += " -M apkroot/AndroidManifest.xml" + aapt_cmd += " -A apkroot/assets -S apkroot/res" + aapt_cmd += " -I $PREFIX/share/aapt/android.jar" + oscmd(aapt_cmd) + + # And add all the libraries to it. + oscmd("cd apkroot && aapt add ../%s classes.dex" % (apk_unaligned)) + for path, dirs, files in os.walk('apkroot/lib'): + if files: + rel = os.path.relpath(path, 'apkroot') + oscmd("cd apkroot && aapt add ../%s %s/*" % (apk_unaligned, rel)) + + # Now align the .apk, which is necessary for Android to load it. + oscmd("zipalign -v -p 4 %s %s" % (apk_unaligned, apk_unsigned)) + + # Finally, sign it using a debug key. This is generated if it doesn't exist. + oscmd("apksigner debug.ks %s panda3d.apk" % (apk_unsigned)) + + # Clean up. + oscmd("rm -rf apkroot") + os.unlink(apk_unaligned) + os.unlink(apk_unsigned) + + +def MakeInstaller(version, **kwargs): + target = GetTarget() + if target == 'windows': + fn = "Panda3D-" + dir = "Panda3D-" + version + + runtime = kwargs.get('runtime', False) + if runtime: + fn += "Runtime-" + title = "Panda3D " + version + else: + title = "Panda3D SDK " + version + + fn += version + + if "PYTHONVERSION" in SDK: + pyver = SDK["PYTHONVERSION"][6:9] + else: + pyver = "%d.%d" % (sys.version_info[:2]) + + if not runtime and pyver != "2.7": + fn += '-py' + pyver + + if GetOptimize() <= 2: + fn += "-dbg" + if GetTargetArch() == 'x64': + fn += '-x64' + dir += '-x64' + + MakeInstallerNSIS(version, fn + '.exe', title, 'C:\\' + dir, **kwargs) + if not runtime: + MakeDebugSymbolArchive(fn + '-pdb.zip', dir) + elif target == 'linux': + MakeInstallerLinux(version, **kwargs) + elif target == 'darwin': + MakeInstallerOSX(version, **kwargs) + elif target == 'freebsd': + MakeInstallerFreeBSD(version, **kwargs) + elif target == 'android': + MakeInstallerAndroid(version, **kwargs) + else: + exit("Do not know how to make an installer for this platform") + + +if __name__ == "__main__": + version = ParsePandaVersion("dtool/PandaVersion.pp") + + parser = OptionParser() + parser.add_option('', '--version', dest='version', help='Panda3D version number (default: %s)' % (version), default=version) + parser.add_option('', '--debversion', dest='debversion', help='Version number for .deb file', default=None) + parser.add_option('', '--rpmrelease', dest='rpmrelease', help='Release number for .rpm file', default='1') + parser.add_option('', '--outputdir', dest='outputdir', help='Makepanda\'s output directory (default: built)', default='built') + parser.add_option('', '--verbose', dest='verbose', help='Enable verbose output', action='store_true', default=False) + parser.add_option('', '--runtime', dest='runtime', help='Runtime instead of SDK', action='store_true', default=False) + parser.add_option('', '--lzma', dest='compressor', help='Use LZMA compression', action='store_const', const='lzma', default='zlib') + (options, args) = parser.parse_args() + + SetVerbose(options.verbose) + SetOutputDir(options.outputdir) + + # Read out the optimize option. + opt = ReadFile(os.path.join(options.outputdir, "tmp", "optimize.dat")) + SetOptimize(int(opt.strip())) + + # Read out whether we should set PkgSkip("PYTHON") and some others. + # Temporary hack; needs better solution. + pkg_list = "PYTHON", "NVIDIACG", "FFMPEG", "OPENAL", "FMODEX", "PVIEW", "NVIDIACG", "VORBIS", "OPUS" + PkgListSet(pkg_list) + for pkg in pkg_list: + dat_path = "dtool_have_%s.dat" % (pkg.lower()) + content = ReadFile(os.path.join(options.outputdir, "tmp", dat_path)) + if int(content.strip()): + PkgEnable(pkg) + else: + PkgDisable(pkg) + + # Parse the version. + match = re.match(r'^\d+\.\d+\.\d+', options.version) + if not match: + exit("version requires three digits") + + MakeInstaller(version=match.group(), + outputdir=options.outputdir, + optimize=GetOptimize(), + compressor=options.compressor, + debversion=options.debversion, + rpmrelease=options.rpmrelease, + runtime=options.runtime) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 4ee16763a3..cb9ab10e0c 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -23,7 +23,6 @@ except: exit(1) from makepandacore import * -from installpanda import * import time import os import sys @@ -2592,6 +2591,11 @@ def WriteConfigSettings(): if (PkgSkip(x)): ConditionalWriteFile(GetOutputDir() + '/tmp/dtool_have_'+x.lower()+'.dat', "0\n") else: ConditionalWriteFile(GetOutputDir() + '/tmp/dtool_have_'+x.lower()+'.dat', "1\n") + # This is useful for tools like makepackage that need to know things about + # the build parameters. + ConditionalWriteFile(GetOutputDir() + '/tmp/optimize.dat', str(GetOptimize())) + + WriteConfigSettings() WarnConflictingFiles() @@ -6715,17 +6719,18 @@ def SequentialMake(tasklist): JustBuilt(task[2], task[3]) i += 1 + def RunDependencyQueue(tasklist): if (THREADCOUNT!=0): ParallelMake(tasklist) else: SequentialMake(tasklist) + try: RunDependencyQueue(DEPENDENCYQUEUE) -except: +finally: SaveDependencyCache() - raise # Run the test suite. if RUNTESTS: @@ -6747,879 +6752,18 @@ if RUNTESTS: # ########################################################################################## -def MakeInstallerNSIS(file, title, installdir): - if (os.path.isfile(file)): - os.remove(file) - elif (os.path.isdir(file)): - shutil.rmtree(file) - - pyver = SDK["PYTHONVERSION"][6:9] - if GetTargetArch() == 'x64': - regview = '64' - else: - regview = '32' - if int(pyver[0]) == 3 and int(pyver[2]) >= 5: - pyver += '-32' - - if (RUNTIME): - # Invoke the make_installer script. - AddToPathEnv("PATH", GetOutputDir() + "\\bin") - AddToPathEnv("PATH", GetOutputDir() + "\\plugins") - - cmd = sys.executable + " -B -u " + os.path.join("direct", "src", "plugin_installer", "make_installer.py") - cmd += " --version %s --regview %s" % (VERSION, regview) - - if GetTargetArch() == 'x64': - cmd += " --install \"$PROGRAMFILES64\\Panda3D\" " - else: - cmd += " --install \"$PROGRAMFILES32\\Panda3D\" " - - oscmd(cmd) - shutil.move(os.path.join("direct", "src", "plugin_installer", "p3d-setup.exe"), file) - return - - print("Building "+title+" installer at %s" % (file)) - if (COMPRESSOR != "lzma"): - print("Note: you are using zlib, which is faster, but lzma gives better compression.") - if (os.path.exists("nsis-output.exe")): - os.remove("nsis-output.exe") - WriteFile(GetOutputDir()+"/tmp/__init__.py", "") - - nsis_defs = { - 'COMPRESSOR' : COMPRESSOR, - 'TITLE' : title, - 'INSTALLDIR' : installdir, - 'OUTFILE' : '..\\' + file, - 'BUILT' : '..\\' + GetOutputDir(), - 'SOURCE' : '..', - 'PYVER' : pyver, - 'REGVIEW' : regview, - 'EXT_SUFFIX' : GetExtensionSuffix(), - } - - if GetHost() == 'windows': - cmd = os.path.join(GetThirdpartyBase(), 'win-nsis', 'makensis') + ' /V2' - for item in nsis_defs.items(): - cmd += ' /D%s="%s"' % item - else: - cmd = 'makensis -V2' - for item in nsis_defs.items(): - cmd += ' -D%s="%s"' % item - - cmd += ' "makepanda\\installer.nsi"' - oscmd(cmd) - -def MakeDebugSymbolArchive(zipname, dirname): - import zipfile - zip = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED) - - for fn in glob.glob(os.path.join(GetOutputDir(), 'bin', '*.pdb')): - zip.write(fn, dirname + '/bin/' + os.path.basename(fn)) - - for fn in glob.glob(os.path.join(GetOutputDir(), 'panda3d', '*.pdb')): - zip.write(fn, dirname + '/panda3d/' + os.path.basename(fn)) - - for fn in glob.glob(os.path.join(GetOutputDir(), 'plugins', '*.pdb')): - zip.write(fn, dirname + '/plugins/' + os.path.basename(fn)) - - for fn in glob.glob(os.path.join(GetOutputDir(), 'python', '*.pdb')): - zip.write(fn, dirname + '/python/' + os.path.basename(fn)) - - for fn in glob.glob(os.path.join(GetOutputDir(), 'python', 'DLLs', '*.pdb')): - zip.write(fn, dirname + '/python/DLLs/' + os.path.basename(fn)) - - zip.close() - -INSTALLER_DEB_FILE=""" -Package: panda3dMAJOR -Version: VERSION -Section: libdevel -Priority: optional -Architecture: ARCH -Essential: no -Depends: DEPENDS -Recommends: RECOMMENDS -Provides: panda3d, pythonPV-panda3d -Conflicts: panda3d, pythonPV-panda3d -Replaces: panda3d, pythonPV-panda3d -Maintainer: rdb -Installed-Size: INSTSIZE -Description: Panda3D free 3D engine SDK - Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost. - Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface. - . - This package contains the SDK for development with Panda3D. - -""" - -RUNTIME_INSTALLER_DEB_FILE=""" -Package: panda3d-runtime -Version: VERSION -Section: web -Priority: optional -Architecture: ARCH -Essential: no -Depends: DEPENDS -Provides: panda3d-runtime -Maintainer: rdb -Installed-Size: INSTSIZE -Description: Runtime binary and browser plugin for the Panda3D Game Engine - This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file. - -""" - - -# We're not putting "python" in the "Requires" field, -# since the rpm-based distros don't have a common -# naming for the Python package. -INSTALLER_SPEC_FILE=""" -Summary: The Panda3D free 3D engine SDK -Name: panda3d -Version: VERSION -Release: RPMRELEASE -License: BSD License -Group: Development/Libraries -BuildRoot: PANDASOURCE/targetroot -%description -Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost. -Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface. - -This package contains the SDK for development with Panda3D, install panda3d-runtime for the runtime files. -%post -/sbin/ldconfig -%postun -/sbin/ldconfig -%files -%defattr(-,root,root) -/etc/Confauto.prc -/etc/Config.prc -/usr/share/panda3d -/etc/ld.so.conf.d/panda3d.conf -/usr/%_lib/panda3d -""" + PYTHON_SITEPACKAGES + """ -/usr/include/panda3d -""" -if not PkgSkip("PVIEW"): - INSTALLER_SPEC_FILE += \ -"""/usr/share/applications/pview.desktop -/usr/share/mime-info/panda3d.mime -/usr/share/mime-info/panda3d.keys -/usr/share/mime/packages/panda3d.xml -/usr/share/application-registry/panda3d.applications -""" - -RUNTIME_INSTALLER_SPEC_FILE=""" -Summary: Runtime binary and browser plugin for the Panda3D Game Engine -Name: panda3d-runtime -Version: VERSION -Release: RPMRELEASE -License: BSD License -Group: Productivity/Graphics/Other -BuildRoot: PANDASOURCE/targetroot -%description -This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file. -%files -%defattr(-,root,root) -/usr/bin/panda3d -/usr/%_lib/nppanda3d.so -/usr/%_lib/mozilla/plugins/nppanda3d.so -/usr/%_lib/mozilla-firefox/plugins/nppanda3d.so -/usr/%_lib/xulrunner-addons/plugins/nppanda3d.so -/usr/share/mime-info/panda3d-runtime.mime -/usr/share/mime-info/panda3d-runtime.keys -/usr/share/mime/packages/panda3d-runtime.xml -/usr/share/application-registry/panda3d-runtime.applications -/usr/share/applications/*.desktop -""" - -# plist file for Mac OSX -Info_plist = """ - - - - CFBundleIdentifier - %(package_id)s - CFBundleShortVersionString - %(version)s - IFPkgFlagRelocatable - - IFPkgFlagAuthorizationAction - RootAuthorization - IFPkgFlagAllowBackRev - - - -""" - -# FreeBSD pkg-descr -INSTALLER_PKG_DESCR_FILE = """ -Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost. -Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface. - -This package contains the SDK for development with Panda3D, install panda3d-runtime for the runtime files. - -WWW: https://www.panda3d.org/ -""" - -# FreeBSD pkg-descr -RUNTIME_INSTALLER_PKG_DESCR_FILE = """ -Runtime binary and browser plugin for the Panda3D Game Engine - -This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file. - -WWW: https://www.panda3d.org/ -""" - -# FreeBSD PKG Manifest template file -INSTALLER_PKG_MANIFEST_FILE = """ -name: NAME -version: VERSION -arch: ARCH -origin: ORIGIN -comment: "Panda3D free 3D engine SDK" -www: https://www.panda3d.org -maintainer: rdb -prefix: /usr/local -flatsize: INSTSIZEMB -deps: {DEPENDS} -""" - -def MakeInstallerLinux(): - if not RUNTIME and not PkgSkip("PYTHON"): - PYTHONV = SDK["PYTHONVERSION"].rstrip('dmu') - else: - PYTHONV = "python" - PV = PYTHONV.replace("python", "") - - # Clean and set up a directory to install Panda3D into - oscmd("rm -rf targetroot data.tar.gz control.tar.gz panda3d.spec") - oscmd("mkdir --mode=0755 targetroot") - - dpkg_present = False - if os.path.exists("/usr/bin/dpkg-architecture") and os.path.exists("/usr/bin/dpkg-deb"): - dpkg_present = True - rpmbuild_present = False - if os.path.exists("/usr/bin/rpmbuild"): - rpmbuild_present = True - - if dpkg_present and rpmbuild_present: - Warn("both dpkg and rpmbuild present.") - - if dpkg_present: - # Invoke installpanda.py to install it into a temporary dir - lib_dir = GetDebLibDir() - if RUNTIME: - InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=GetOutputDir(), libdir=lib_dir) - else: - InstallPanda(destdir="targetroot", prefix="/usr", outputdir=GetOutputDir(), libdir=lib_dir) - oscmd("chmod -R 755 targetroot/usr/share/panda3d") - oscmd("mkdir -m 0755 -p targetroot/usr/share/man/man1") - oscmd("install -m 0644 doc/man/*.1 targetroot/usr/share/man/man1/") - - oscmd("dpkg --print-architecture > "+GetOutputDir()+"/tmp/architecture.txt") - pkg_arch = ReadFile(GetOutputDir()+"/tmp/architecture.txt").strip() - if (RUNTIME): - txt = RUNTIME_INSTALLER_DEB_FILE[1:] - else: - txt = INSTALLER_DEB_FILE[1:] - txt = txt.replace("VERSION", DEBVERSION).replace("ARCH", pkg_arch).replace("PV", PV).replace("MAJOR", MAJOR_VERSION) - txt = txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024)) - oscmd("mkdir --mode=0755 -p targetroot/DEBIAN") - oscmd("cd targetroot && (find usr -type f -exec md5sum {} ;) > DEBIAN/md5sums") - if (not RUNTIME): - oscmd("cd targetroot && (find etc -type f -exec md5sum {} ;) >> DEBIAN/md5sums") - WriteFile("targetroot/DEBIAN/conffiles","/etc/Config.prc\n") - WriteFile("targetroot/DEBIAN/postinst","#!/bin/sh\necho running ldconfig\nldconfig\n") - oscmd("cp targetroot/DEBIAN/postinst targetroot/DEBIAN/postrm") - - # Determine the package name and the locations that - # dpkg-shlibdeps should look in for executables. - pkg_version = DEBVERSION - if RUNTIME: - pkg_name = "panda3d-runtime" - lib_pattern = "debian/%s/usr/%s/*.so" % (pkg_name, lib_dir) - else: - pkg_name = "panda3d" + MAJOR_VERSION - lib_pattern = "debian/%s/usr/%s/panda3d/*.so*" % (pkg_name, lib_dir) - bin_pattern = "debian/%s/usr/bin/*" % (pkg_name) - - # dpkg-shlibdeps looks in the debian/{pkg_name}/DEBIAN/shlibs directory - # and also expects a debian/control file, so we create this dummy set-up. - oscmd("mkdir targetroot/debian") - oscmd("ln -s .. targetroot/debian/" + pkg_name) - WriteFile("targetroot/debian/control", "") - - dpkg_shlibdeps = "dpkg-shlibdeps" - if GetVerbose(): - dpkg_shlibdeps += " -v" - - if RUNTIME: - # The runtime doesn't export any useful symbols, so just query the dependencies. - oscmd("cd targetroot && %(dpkg_shlibdeps)s -x%(pkg_name)s %(lib_pattern)s %(bin_pattern)s*" % locals()) - depends = ReadFile("targetroot/debian/substvars").replace("shlibs:Depends=", "").strip() - recommends = "" - else: - pkg_name = "panda3d" + MAJOR_VERSION - pkg_dir = "debian/panda3d" + MAJOR_VERSION - - # Generate a symbols file so that other packages can know which symbols we export. - oscmd("cd targetroot && dpkg-gensymbols -q -ODEBIAN/symbols -v%(pkg_version)s -p%(pkg_name)s -e%(lib_pattern)s" % locals()) - - # Library dependencies are required, binary dependencies are recommended. - # We explicitly exclude libphysx-extras since we don't want to depend on PhysX. - oscmd("cd targetroot && LD_LIBRARY_PATH=usr/%(lib_dir)s/panda3d %(dpkg_shlibdeps)s -Tdebian/substvars_dep --ignore-missing-info -x%(pkg_name)s -xlibphysx-extras %(lib_pattern)s" % locals()) - oscmd("cd targetroot && LD_LIBRARY_PATH=usr/%(lib_dir)s/panda3d %(dpkg_shlibdeps)s -Tdebian/substvars_rec --ignore-missing-info -x%(pkg_name)s %(bin_pattern)s" % locals()) - - # Parse the substvars files generated by dpkg-shlibdeps. - depends = ReadFile("targetroot/debian/substvars_dep").replace("shlibs:Depends=", "").strip() - recommends = ReadFile("targetroot/debian/substvars_rec").replace("shlibs:Depends=", "").strip() - if PkgSkip("PYTHON")==0: - depends += ", " + PYTHONV - recommends += ", python-wxversion, python-profiler (>= " + PV + "), python-pmw, python-tk (>= " + PV + ")" - if PkgSkip("NVIDIACG")==0: - depends += ", nvidia-cg-toolkit" - - # Write back the dependencies, and delete the dummy set-up. - txt = txt.replace("DEPENDS", depends.strip(', ')) - txt = txt.replace("RECOMMENDS", recommends.strip(', ')) - WriteFile("targetroot/DEBIAN/control", txt) - oscmd("rm -rf targetroot/debian") - - # Package it all up into a .deb file. - oscmd("chmod -R 755 targetroot/DEBIAN") - oscmd("chmod 644 targetroot/DEBIAN/control targetroot/DEBIAN/md5sums") - if not RUNTIME: - oscmd("chmod 644 targetroot/DEBIAN/conffiles targetroot/DEBIAN/symbols") - oscmd("fakeroot dpkg-deb -b targetroot %s_%s_%s.deb" % (pkg_name, pkg_version, pkg_arch)) - - elif rpmbuild_present: - # Invoke installpanda.py to install it into a temporary dir - if RUNTIME: - InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=GetOutputDir(), libdir=GetRPMLibDir()) - else: - InstallPanda(destdir="targetroot", prefix="/usr", outputdir=GetOutputDir(), libdir=GetRPMLibDir()) - oscmd("chmod -R 755 targetroot/usr/share/panda3d") - - oscmd("rpm -E '%_target_cpu' > "+GetOutputDir()+"/tmp/architecture.txt") - ARCH = ReadFile(GetOutputDir()+"/tmp/architecture.txt").strip() - pandasource = os.path.abspath(os.getcwd()) - - if RUNTIME: - txt = RUNTIME_INSTALLER_SPEC_FILE[1:] - else: - txt = INSTALLER_SPEC_FILE[1:] - # Add the binaries in /usr/bin explicitly to the spec file - for base in os.listdir(GetOutputDir() + "/bin"): - txt += "/usr/bin/%s\n" % (base) - - # Write out the spec file. - txt = txt.replace("VERSION", VERSION) - txt = txt.replace("RPMRELEASE", RPMRELEASE) - txt = txt.replace("PANDASOURCE", pandasource) - txt = txt.replace("PV", PV) - WriteFile("panda3d.spec", txt) - - oscmd("fakeroot rpmbuild --define '_rpmdir "+pandasource+"' --buildroot '"+os.path.abspath("targetroot")+"' -bb panda3d.spec") - if (RUNTIME): - oscmd("mv "+ARCH+"/panda3d-runtime-"+VERSION+"-"+RPMRELEASE+"."+ARCH+".rpm .") - else: - oscmd("mv "+ARCH+"/panda3d-"+VERSION+"-"+RPMRELEASE+"."+ARCH+".rpm .") - oscmd("rm -rf "+ARCH, True) - - else: - exit("To build an installer, either rpmbuild or dpkg-deb must be present on your system!") - -def MakeInstallerOSX(): - if (RUNTIME): - # Invoke the make_installer script. - AddToPathEnv("DYLD_LIBRARY_PATH", GetOutputDir() + "/plugins") - cmdstr = sys.executable + " " - if sys.version_info >= (2, 6): - cmdstr += "-B " - - cmdstr += "direct/src/plugin_installer/make_installer.py --version %s" % VERSION - oscmd(cmdstr) - return - - dmg_name = "Panda3D-" + VERSION - if not SDK["PYTHONVERSION"].startswith("python2."): - dmg_name += '-py' + SDK["PYTHONVERSION"][6:9] - dmg_name += ".dmg" - - import compileall - if (os.path.isfile(dmg_name)): oscmd("rm -f %s" % dmg_name) - if (os.path.exists("dstroot")): oscmd("rm -rf dstroot") - if (os.path.exists("Panda3D-rw.dmg")): oscmd('rm -f Panda3D-rw.dmg') - - oscmd("mkdir -p dstroot/base/Developer/Panda3D/lib") - oscmd("mkdir -p dstroot/base/Developer/Panda3D/etc") - oscmd("cp %s/etc/Config.prc dstroot/base/Developer/Panda3D/etc/Config.prc" % GetOutputDir()) - oscmd("cp %s/etc/Confauto.prc dstroot/base/Developer/Panda3D/etc/Confauto.prc" % GetOutputDir()) - oscmd("cp -R %s/models dstroot/base/Developer/Panda3D/models" % GetOutputDir()) - oscmd("cp -R doc/LICENSE dstroot/base/Developer/Panda3D/LICENSE") - oscmd("cp -R doc/ReleaseNotes dstroot/base/Developer/Panda3D/ReleaseNotes") - oscmd("cp -R %s/Frameworks dstroot/base/Developer/Panda3D/Frameworks" % GetOutputDir()) - if os.path.isdir(GetOutputDir()+"/plugins"): - oscmd("cp -R %s/plugins dstroot/base/Developer/Panda3D/plugins" % GetOutputDir()) - - # Libraries that shouldn't be in base, but are instead in other modules. - no_base_libs = ['libp3ffmpeg', 'libp3fmod_audio', 'libfmodex', 'libfmodexL'] - - for base in os.listdir(GetOutputDir()+"/lib"): - if not base.endswith(".a") and base.split('.')[0] not in no_base_libs: - libname = "dstroot/base/Developer/Panda3D/lib/" + base - # We really need to specify -R in order not to follow symlinks - # On OSX, just specifying -P is not enough to do that. - oscmd("cp -R -P " + GetOutputDir() + "/lib/" + base + " " + libname) - - oscmd("mkdir -p dstroot/tools/Developer/Panda3D/bin") - oscmd("mkdir -p dstroot/tools/Developer/Tools") - oscmd("ln -s ../Panda3D/bin dstroot/tools/Developer/Tools/Panda3D") - oscmd("mkdir -p dstroot/tools/etc/paths.d") - # Trailing newline is important, works around a bug in OSX - WriteFile("dstroot/tools/etc/paths.d/Panda3D", "/Developer/Panda3D/bin\n") - - oscmd("mkdir -m 0755 -p dstroot/tools/usr/local/share/man/man1") - oscmd("install -m 0644 doc/man/*.1 dstroot/tools/usr/local/share/man/man1/") - - for base in os.listdir(GetOutputDir()+"/bin"): - binname = "dstroot/tools/Developer/Panda3D/bin/" + base - # OSX needs the -R argument to copy symbolic links correctly, it doesn't have -d. How weird. - oscmd("cp -R " + GetOutputDir() + "/bin/" + base + " " + binname) - - if PkgSkip("PYTHON")==0: - PV = SDK["PYTHONVERSION"][6:9] - oscmd("mkdir -p dstroot/pythoncode/usr/local/bin") - oscmd("mkdir -p dstroot/pythoncode/Developer/Panda3D/panda3d") - oscmd("mkdir -p dstroot/pythoncode/Library/Python/%s/site-packages" % PV) - WriteFile("dstroot/pythoncode/Library/Python/%s/site-packages/Panda3D.pth" % PV, "/Developer/Panda3D") - oscmd("cp -R %s/pandac dstroot/pythoncode/Developer/Panda3D/pandac" % GetOutputDir()) - oscmd("cp -R %s/direct dstroot/pythoncode/Developer/Panda3D/direct" % GetOutputDir()) - oscmd("ln -s %s dstroot/pythoncode/usr/local/bin/ppython" % SDK["PYTHONEXEC"]) - oscmd("cp -R %s/*.so dstroot/pythoncode/Developer/Panda3D/" % GetOutputDir(), True) - oscmd("cp -R %s/*.py dstroot/pythoncode/Developer/Panda3D/" % GetOutputDir(), True) - if os.path.isdir(GetOutputDir()+"/Pmw"): - oscmd("cp -R %s/Pmw dstroot/pythoncode/Developer/Panda3D/Pmw" % GetOutputDir()) - compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/Pmw") - WriteFile("dstroot/pythoncode/Developer/Panda3D/direct/__init__.py", "") - for base in os.listdir("dstroot/pythoncode/Developer/Panda3D/direct"): - if ((base != "extensions") and (base != "extensions_native")): - compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/direct/"+base) - - suffix = GetExtensionSuffix() - for base in os.listdir(GetOutputDir()+"/panda3d"): - if base.endswith('.py') or (base.endswith(suffix) and '.' not in base[:-len(suffix)]): - libname = "dstroot/pythoncode/Developer/Panda3D/panda3d/" + base - # We really need to specify -R in order not to follow symlinks - # On OSX, just specifying -P is not enough to do that. - oscmd("cp -R -P " + GetOutputDir() + "/panda3d/" + base + " " + libname) - - if not PkgSkip("FFMPEG"): - oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib") - oscmd("cp -R %s/lib/libp3ffmpeg.* dstroot/ffmpeg/Developer/Panda3D/lib/" % GetOutputDir()) - - #if not PkgSkip("OPENAL"): - # oscmd("mkdir -p dstroot/openal/Developer/Panda3D/lib") - # oscmd("cp -R %s/lib/libp3openal_audio.* dstroot/openal/Developer/Panda3D/lib/" % GetOutputDir()) - - if not PkgSkip("FMODEX"): - oscmd("mkdir -p dstroot/fmodex/Developer/Panda3D/lib") - oscmd("cp -R %s/lib/libp3fmod_audio.* dstroot/fmodex/Developer/Panda3D/lib/" % GetOutputDir()) - oscmd("cp -R %s/lib/libfmodex* dstroot/fmodex/Developer/Panda3D/lib/" % GetOutputDir()) - - oscmd("mkdir -p dstroot/headers/Developer/Panda3D/lib") - oscmd("cp -R %s/include dstroot/headers/Developer/Panda3D/include" % GetOutputDir()) - if os.path.isfile(GetOutputDir() + "/lib/libp3pystub.a"): - oscmd("cp -R -P %s/lib/libp3pystub.a dstroot/headers/Developer/Panda3D/lib/" % GetOutputDir()) - - if os.path.isdir("samples"): - oscmd("mkdir -p dstroot/samples/Developer/Examples/Panda3D") - oscmd("cp -R samples/* dstroot/samples/Developer/Examples/Panda3D/") - - oscmd("chmod -R 0775 dstroot/*") - DeleteVCS("dstroot") - DeleteBuildFiles("dstroot") - # We need to be root to perform a chown. Bleh. - # Fortunately PackageMaker does it for us, on 10.5 and above. - #oscmd("chown -R root:admin dstroot/*", True) - - oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/") - oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Resources/en.lproj/") - - pkgs = ["base", "tools", "headers"] - if not PkgSkip("PYTHON"): pkgs.append("pythoncode") - if not PkgSkip("FFMPEG"): pkgs.append("ffmpeg") - #if not PkgSkip("OPENAL"): pkgs.append("openal") - if not PkgSkip("FMODEX"): pkgs.append("fmodex") - if os.path.isdir("samples"): pkgs.append("samples") - for pkg in pkgs: - identifier = "org.panda3d.panda3d.%s.pkg" % pkg - plist = open("/tmp/Info_plist", "w") - plist.write(Info_plist % { "package_id" : identifier, "version" : VERSION }) - plist.close() - if not os.path.isdir("dstroot/" + pkg): - os.makedirs("dstroot/" + pkg) - - if OSXTARGET and OSXTARGET <= (10, 5): - target = '--target %d.%d' % (OSXTARGET) - else: - target = '' - - if os.path.exists("/usr/bin/pkgbuild"): - # This new package builder is used in Lion and above. - cmd = '/usr/bin/pkgbuild --identifier ' + identifier + ' --version ' + VERSION + ' --root dstroot/' + pkg + '/ dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg' - - # In older versions, we use PackageMaker. Apple keeps changing its location. - elif os.path.exists("/Developer/usr/bin/packagemaker"): - cmd = '/Developer/usr/bin/packagemaker --info /tmp/Info_plist --version ' + VERSION + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate' - elif os.path.exists("/Applications/Xcode.app/Contents/Applications/PackageMaker.app/Contents/MacOS/PackageMaker"): - cmd = '/Applications/Xcode.app/Contents/Applications/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + VERSION + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate' - elif os.path.exists("/Developer/Tools/PackageMaker.app/Contents/MacOS/PackageMaker"): - cmd = '/Developer/Tools/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + VERSION + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate' - elif os.path.exists("/Developer/Tools/packagemaker"): - cmd = '/Developer/Tools/packagemaker -build -f dstroot/' + pkg + '/ -p dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg -i /tmp/Info_plist' - elif os.path.exists("/Applications/PackageMaker.app/Contents/MacOS/PackageMaker"): - cmd = '/Applications/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + VERSION + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate' - else: - exit("Neither pkgbuild nor PackageMaker could be found!") - oscmd(cmd) - - if os.path.isfile("/tmp/Info_plist"): - oscmd("rm -f /tmp/Info_plist") - - # Now that we've built all of the individual packages, build the metapackage. - dist = open("dstroot/Panda3D/Panda3D.mpkg/Contents/distribution.dist", "w") - dist.write('\n') - dist.write('\n') - dist.write(' Panda3D SDK %s\n' % (VERSION)) - dist.write(' \n') - dist.write(' %s\n' % ReadFile("doc/LICENSE")) - dist.write(' \n') - for pkg in pkgs: - dist.write(' \n' % (pkg)) - dist.write(' \n') - dist.write(' \n') - dist.write(' \n') - dist.write(' \n') - dist.write(' \n') - dist.write(' \n') - dist.write(' \n') - - if not PkgSkip("PYTHON"): - dist.write(' \n') - dist.write(' \n') - dist.write(' \n') - - if not PkgSkip("FFMPEG"): - dist.write(' \n') - elif PkgSkip("VORBIS"): - dist.write(' It is not required for loading .wav or .opus files, which Panda3D can read out of the box.">\n') - elif PkgSkip("OPUS"): - dist.write(' It is not required for loading .wav or .ogg files, which Panda3D can read out of the box.">\n') - else: - dist.write(' It is not required for loading .wav, .ogg or .opus files, which Panda3D can read out of the box.">\n') - dist.write(' \n') - dist.write(' \n') - - #if not PkgSkip("OPENAL"): - # dist.write(' \n') - # dist.write(' \n') - # dist.write(' \n') - - if not PkgSkip("FMODEX"): - dist.write(' \n') - dist.write(' \n') - dist.write(' \n') - - if os.path.isdir("samples"): - dist.write(' \n') - dist.write(' \n') - dist.write(' \n') - - dist.write(' \n') - dist.write(' \n') - dist.write(' \n') - for pkg in pkgs: - size = GetDirectorySize("dstroot/" + pkg) // 1024 - dist.write(' file:./Contents/Packages/%s.pkg\n' % (pkg, size, pkg)) - dist.write('\n') - dist.close() - - oscmd('hdiutil create Panda3D-rw.dmg -volname "Panda3D SDK %s" -srcfolder dstroot/Panda3D' % (VERSION)) - oscmd('hdiutil convert Panda3D-rw.dmg -format UDBZ -o %s' % (dmg_name)) - oscmd('rm -f Panda3D-rw.dmg') - -def MakeInstallerFreeBSD(): - oscmd("rm -rf targetroot +DESC pkg-plist +MANIFEST") - oscmd("mkdir targetroot") - - # Invoke installpanda.py to install it into a temporary dir - if RUNTIME: - InstallRuntime(destdir = "targetroot", prefix = "/usr/local", outputdir = GetOutputDir()) - else: - InstallPanda(destdir = "targetroot", prefix = "/usr/local", outputdir = GetOutputDir()) - - if not os.path.exists("/usr/sbin/pkg"): - exit("Cannot create an installer without pkg") - - plist_txt = '' - for root, dirs, files in os.walk("targetroot/usr/local/", True): - for f in files: - plist_txt += os.path.join(root, f)[21:] + "\n" - - if not RUNTIME: - plist_txt += "@postexec /sbin/ldconfig -m /usr/local/lib/panda3d\n" - plist_txt += "@postunexec /sbin/ldconfig -R\n" - - for remdir in ("lib/panda3d", "share/panda3d", "include/panda3d"): - for root, dirs, files in os.walk("targetroot/usr/local/" + remdir, False): - for d in dirs: - plist_txt += "@dir %s\n" % os.path.join(root, d)[21:] - plist_txt += "@dir %s\n" % remdir - - oscmd("echo \"`pkg config abi | tr '[:upper:]' '[:lower:]' | cut -d: -f1,2`:*\" > " + GetOutputDir() + "/tmp/architecture.txt") - pkg_arch = ReadFile(GetOutputDir()+"/tmp/architecture.txt").strip() - - dependencies = '' - if PkgSkip("PYTHON") == 0: - # If this version of Python was installed from a package or ports, let's mark it as dependency. - oscmd("rm -f %s/tmp/python_dep" % GetOutputDir()) - oscmd("pkg query \"\n\t%%n : {\n\t\torigin : %%o,\n\t\tversion : %%v\n\t},\n\" python%s > %s/tmp/python_dep" % (SDK["PYTHONVERSION"][6:9:2], GetOutputDir()), True) - if os.path.isfile(GetOutputDir() + "/tmp/python_dep"): - python_pkg = ReadFile(GetOutputDir() + "/tmp/python_dep") - if python_pkg: - dependencies += python_pkg - - manifest_txt = INSTALLER_PKG_MANIFEST_FILE[1:].replace("NAME", 'panda3d' if not RUNTIME else 'panda3d-runtime') - manifest_txt = manifest_txt.replace("VERSION", VERSION) - manifest_txt = manifest_txt.replace("ARCH", pkg_arch) - manifest_txt = manifest_txt.replace("ORIGIN", 'devel/panda3d' if not RUNTIME else 'graphics/panda3d-runtime') - manifest_txt = manifest_txt.replace("DEPENDS", dependencies) - manifest_txt = manifest_txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024 / 1024)) - - WriteFile("pkg-plist", plist_txt) - WriteFile("+DESC", INSTALLER_PKG_DESCR_FILE[1:] if not RUNTIME else RUNTIME_INSTALLER_PKG_DESCR_FILE[1:]) - WriteFile("+MANIFEST", manifest_txt) - oscmd("pkg create -p pkg-plist -r %s -m . -o . %s" % (os.path.abspath("targetroot"), "--verbose" if GetVerbose() else "--quiet")) - -def MakeInstallerAndroid(): - oscmd("rm -rf apkroot") - oscmd("mkdir apkroot") - - # Also remove the temporary apks. - apk_unaligned = os.path.join(GetOutputDir(), "tmp", "panda3d-unaligned.apk") - apk_unsigned = os.path.join(GetOutputDir(), "tmp", "panda3d-unsigned.apk") - if os.path.exists(apk_unaligned): - os.unlink(apk_unaligned) - if os.path.exists(apk_unsigned): - os.unlink(apk_unsigned) - - # Compile the Java classes into a Dalvik executable. - dx_cmd = "dx --dex --output=apkroot/classes.dex " - if GetOptimize() <= 2: - dx_cmd += "--debug " - if GetVerbose(): - dx_cmd += "--verbose " - if "ANDROID_API" in SDK: - dx_cmd += "--min-sdk-version=%d " % (SDK["ANDROID_API"]) - dx_cmd += os.path.join(GetOutputDir(), "classes") - oscmd(dx_cmd) - - # Copy the libraries one by one. In case of library dependencies, strip - # off any suffix (eg. libfile.so.1.0), as Android does not support them. - source_dir = os.path.join(GetOutputDir(), "lib") - target_dir = os.path.join("apkroot", "lib", SDK["ANDROID_ABI"]) - oscmd("mkdir -p %s" % (target_dir)) - - # Determine the library directories we should look in. - libpath = [source_dir] - for dir in os.environ.get("LD_LIBRARY_PATH", "").split(':'): - dir = os.path.expandvars(dir) - dir = os.path.expanduser(dir) - if os.path.isdir(dir): - dir = os.path.realpath(dir) - if not dir.startswith("/system") and not dir.startswith("/vendor"): - libpath.append(dir) - - def copy_library(source, base): - # Copy file to destination, stripping version suffix. - target = os.path.join(target_dir, base) - if not target.endswith('.so'): - target = target.rpartition('.so.')[0] + '.so' - - if os.path.isfile(target): - # Already processed. - return - - oscmd("cp %s %s" % (source, target)) - - # Walk through the library dependencies. - oscmd("ldd %s | grep .so > %s/tmp/otool-libs.txt" % (target, GetOutputDir()), True) - for line in open(GetOutputDir() + "/tmp/otool-libs.txt", "r"): - line = line.strip() - if not line: - continue - if '.so.' in line: - dep = line.rpartition('.so.')[0] + '.so' - oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target), True) - else: - dep = line - - # Find it on the LD_LIBRARY_PATH. - for dir in libpath: - fulldep = os.path.join(dir, dep) - if os.path.isfile(fulldep): - copy_library(os.path.realpath(fulldep), dep) - break - - # Now copy every lib in the lib dir, and its dependencies. - for base in os.listdir(source_dir): - if not base.startswith('lib'): - continue - if not base.endswith('.so') and '.so.' not in base: - continue - - source = os.path.join(source_dir, base) - if os.path.islink(source): - continue - copy_library(source, base) - - # Same for Python extension modules. However, Android is strict about - # library naming, so we have a special naming scheme for these, in - # conjunction with a custom import hook to find these modules. - if not PkgSkip("PYTHON"): - suffix = GetExtensionSuffix() - source_dir = os.path.join(GetOutputDir(), "panda3d") - for base in os.listdir(source_dir): - if not base.endswith(suffix): - continue - modname = base[:-len(suffix)] - if '.' not in modname: - source = os.path.join(source_dir, base) - copy_library(source, "libpy.panda3d.{}.so".format(modname)) - - # Same for standard Python modules. - import _ctypes - source_dir = os.path.dirname(_ctypes.__file__) - for base in os.listdir(source_dir): - if not base.endswith('.so'): - continue - modname = base.partition('.')[0] - source = os.path.join(source_dir, base) - copy_library(source, "libpy.{}.so".format(modname)) - - def copy_python_tree(source_root, target_root): - for source_dir, dirs, files in os.walk(source_root): - if 'site-packages' in dirs: - dirs.remove('site-packages') - - if not any(base.endswith('.py') for base in files): - continue - - target_dir = os.path.join(target_root, os.path.relpath(source_dir, source_root)) - target_dir = os.path.normpath(target_dir) - os.makedirs(target_dir, 0o755) - - for base in files: - if base.endswith('.py'): - target = os.path.join(target_dir, base) - shutil.copy(os.path.join(source_dir, base), target) - - # Copy the Python standard library to the .apk as well. - from distutils.sysconfig import get_python_lib - stdlib_source = get_python_lib(False, True) - stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info)) - copy_python_tree(stdlib_source, stdlib_target) - - # But also copy over our custom site.py. - shutil.copy("panda/src/android/site.py", os.path.join(stdlib_target, "site.py")) - - # And now make a site-packages directory containing our direct/panda3d/pandac modules. - for tree in "panda3d", "direct", "pandac": - copy_python_tree(os.path.join(GetOutputDir(), tree), os.path.join(stdlib_target, "site-packages", tree)) - - # Copy the models and config files to the virtual assets filesystem. - oscmd("mkdir apkroot/assets") - oscmd("cp -R %s apkroot/assets/models" % (os.path.join(GetOutputDir(), "models"))) - oscmd("cp -R %s apkroot/assets/etc" % (os.path.join(GetOutputDir(), "etc"))) - - # Make an empty res folder. It's needed for the apk to be installable, apparently. - oscmd("mkdir apkroot/res") - - # Now package up the application - oscmd("cp panda/src/android/pview_manifest.xml apkroot/AndroidManifest.xml") - aapt_cmd = "aapt package" - aapt_cmd += " -F %s" % (apk_unaligned) - aapt_cmd += " -M apkroot/AndroidManifest.xml" - aapt_cmd += " -A apkroot/assets -S apkroot/res" - aapt_cmd += " -I $PREFIX/share/aapt/android.jar" - oscmd(aapt_cmd) - - # And add all the libraries to it. - oscmd("cd apkroot && aapt add ../%s classes.dex" % (apk_unaligned)) - for path, dirs, files in os.walk('apkroot/lib'): - if files: - rel = os.path.relpath(path, 'apkroot') - oscmd("cd apkroot && aapt add ../%s %s/*" % (apk_unaligned, rel)) - - # Now align the .apk, which is necessary for Android to load it. - oscmd("zipalign -v -p 4 %s %s" % (apk_unaligned, apk_unsigned)) - - # Finally, sign it using a debug key. This is generated if it doesn't exist. - oscmd("apksigner debug.ks %s panda3d.apk" % (apk_unsigned)) - - # Clean up. - oscmd("rm -rf apkroot") - os.unlink(apk_unaligned) - os.unlink(apk_unsigned) - -try: - if INSTALLER: - ProgressOutput(100.0, "Building installer") - target = GetTarget() - if target == 'windows': - fn = "Panda3D-" - dir = "Panda3D-" + VERSION - - if RUNTIME: - fn += "Runtime-" - title = "Panda3D " + VERSION - else: - title = "Panda3D SDK " + VERSION - - fn += VERSION - - if not RUNTIME and SDK["PYTHONVERSION"] != "python2.7": - fn += '-py' + SDK["PYTHONVERSION"][6:] - - if GetOptimize() <= 2: - fn += "-dbg" - if GetTargetArch() == 'x64': - fn += '-x64' - dir += '-x64' - - MakeInstallerNSIS(fn + '.exe', title, 'C:\\' + dir) - if not RUNTIME: - MakeDebugSymbolArchive(fn + '-pdb.zip', dir) - elif (target == 'linux'): - MakeInstallerLinux() - elif (target == 'darwin'): - MakeInstallerOSX() - elif (target == 'freebsd'): - MakeInstallerFreeBSD() - elif (target == 'android'): - MakeInstallerAndroid() - else: - exit("Do not know how to make an installer for this platform") - - if WHEEL: - ProgressOutput(100.0, "Building wheel") - from makewheel import makewheel - makewheel(WHLVERSION, GetOutputDir()) -finally: - SaveDependencyCache() +if INSTALLER: + ProgressOutput(100.0, "Building installer") + from makepackage import MakeInstaller + MakeInstaller(version=VERSION, outputdir=GetOutputDir(), + optimize=GetOptimize(), compressor=COMPRESSOR, + debversion=DEBVERSION, rpmrelease=RPMRELEASE, + runtime=RUNTIME) + +if WHEEL: + ProgressOutput(100.0, "Building wheel") + from makewheel import makewheel + makewheel(WHLVERSION, GetOutputDir()) ########################################################################################## # From 68b798655727d1f49582b431a01fcfb7d0f98529 Mon Sep 17 00:00:00 2001 From: loblao <12ksit@gmail.com> Date: Sun, 9 Dec 2018 14:09:30 -0200 Subject: [PATCH 325/424] NodePath: Implement replace_material Closes #472 --- panda/src/pgraph/nodePath.cxx | 60 +++++++++++++++++++++++++++++++++++ panda/src/pgraph/nodePath.h | 3 ++ 2 files changed, 63 insertions(+) diff --git a/panda/src/pgraph/nodePath.cxx b/panda/src/pgraph/nodePath.cxx index 9d473304b6..1832e18e5a 100644 --- a/panda/src/pgraph/nodePath.cxx +++ b/panda/src/pgraph/nodePath.cxx @@ -4163,6 +4163,20 @@ get_material() const { return nullptr; } +/** + * Recursively searches the scene graph for references to the given material, + * and replaces them with the new material. + */ +void NodePath:: +replace_material(Material *mat, Material *new_mat) { + nassertv_always(!is_empty()); + nassertv(mat != nullptr); + nassertv(new_mat != nullptr); + + CPT(RenderAttrib) new_attrib = MaterialAttrib::make(new_mat); + r_replace_material(node(), mat, (const MaterialAttrib *)new_attrib.p()); +} + /** * Sets the geometry at this level and below to render using the indicated * fog. @@ -6632,6 +6646,52 @@ r_find_all_materials(PandaNode *node, const RenderState *state, } } +/** + * + */ +void NodePath:: +r_replace_material(PandaNode *node, Material *mat, + const MaterialAttrib *new_attrib) { + // Consider the state of the node itself. + { + CPT(RenderState) node_state = node->get_state(); + const MaterialAttrib *ma; + if (node_state->get_attrib(ma)) { + if (mat == ma->get_material()) { + node->set_state(node_state->set_attrib(new_attrib)); + } + } + } + + // If this is a GeomNode, consider the state of any of its Geoms. + if (node->is_geom_node()) { + GeomNode *gnode; + DCAST_INTO_V(gnode, node); + + int num_geoms = gnode->get_num_geoms(); + for (int i = 0; i < num_geoms; i++) { + CPT(RenderState) geom_state = gnode->get_geom_state(i); + + // Look for a MaterialAttrib on the state. + const MaterialAttrib *ma; + if (geom_state->get_attrib(ma)) { + if (mat == ma->get_material()) { + // Replace it + gnode->set_geom_state(i, geom_state->set_attrib(new_attrib)); + } + } + } + } + + // Now consider children. + PandaNode::Children cr = node->get_children(); + size_t num_children = cr.get_num_children(); + for (size_t i = 0; i < num_children; ++i) { + PandaNode *child = cr.get_child(i); + r_replace_material(child, mat, new_attrib); + } +} + /** * Writes the contents of this object to the datagram for shipping out to a * Bam file. diff --git a/panda/src/pgraph/nodePath.h b/panda/src/pgraph/nodePath.h index 9c047c0728..3d5d4a6aa3 100644 --- a/panda/src/pgraph/nodePath.h +++ b/panda/src/pgraph/nodePath.h @@ -771,6 +771,7 @@ PUBLISHED: void clear_material(); bool has_material() const; PT(Material) get_material() const; + void replace_material(Material *mat, Material *new_mat); void set_fog(Fog *fog, int priority = 0); void set_fog_off(int priority = 0); @@ -1016,6 +1017,8 @@ private: const GlobPattern &glob) const; void r_find_all_materials(PandaNode *node, const RenderState *state, Materials &materials) const; + static void r_replace_material(PandaNode *node, Material *mat, + const MaterialAttrib *new_attrib); PT(NodePathComponent) _head; int _backup_key; From b5b77e59568eead1eee5b2407f124b432f91d7f3 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 20:16:55 +0100 Subject: [PATCH 326/424] pgraph: fix issues with serializing empty NodePaths --- panda/src/pgraph/config_pgraph.cxx | 1 + panda/src/pgraph/nodePath.cxx | 9 +++++++- tests/pgraph/test_nodepath.py | 34 ++++++++++++++++++++++++++---- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/panda/src/pgraph/config_pgraph.cxx b/panda/src/pgraph/config_pgraph.cxx index e969dee72f..5729e22067 100644 --- a/panda/src/pgraph/config_pgraph.cxx +++ b/panda/src/pgraph/config_pgraph.cxx @@ -492,6 +492,7 @@ init_libpgraph() { ModelNode::register_with_read_factory(); ModelRoot::register_with_read_factory(); PandaNode::register_with_read_factory(); + ParamNodePath::register_with_read_factory(); PlaneNode::register_with_read_factory(); PolylightNode::register_with_read_factory(); PortalNode::register_with_read_factory(); diff --git a/panda/src/pgraph/nodePath.cxx b/panda/src/pgraph/nodePath.cxx index 1832e18e5a..8732168212 100644 --- a/panda/src/pgraph/nodePath.cxx +++ b/panda/src/pgraph/nodePath.cxx @@ -5691,7 +5691,9 @@ encode_to_bam_stream(vector_uchar &data, BamWriter *writer) const { // Tell the BamWriter which node is the root node, for making NodePaths // relative to when writing them out to the file. - writer->set_root_node(node()); + if (!is_empty()) { + writer->set_root_node(node()); + } // Write an initial Datagram to represent the error type and number of // nodes. @@ -6698,6 +6700,11 @@ r_replace_material(PandaNode *node, Material *mat, */ void NodePath:: write_datagram(BamWriter *manager, Datagram &dg) const { + if (is_empty()) { + manager->write_pointer(dg, nullptr); + return; + } + PandaNode *root = DCAST(PandaNode, manager->get_root_node()); // We have no root node to measure from. diff --git a/tests/pgraph/test_nodepath.py b/tests/pgraph/test_nodepath.py index 1b98da32d7..19a40021ca 100644 --- a/tests/pgraph/test_nodepath.py +++ b/tests/pgraph/test_nodepath.py @@ -2,13 +2,39 @@ import pytest, sys def test_nodepath_empty(): """Tests NodePath behavior for empty NodePaths.""" + from panda3d.core import NodePath, ParamNodePath + import pickle + + empty = NodePath() + assert empty.is_empty() + assert not empty + + # Try pickling, which uses __reduce__ + dumped = pickle.dumps(empty) + empty2 = pickle.loads(dumped) + assert empty2.is_empty() + assert not empty2 + assert empty == empty2 + + # Test write_datagram/fillin, which are invoked when the NodePath is being + # serialized indirectly, such as via ParamNodePath + dumped = pickle.dumps(ParamNodePath(empty)) + empty2 = pickle.loads(dumped).get_value() + assert empty2.is_empty() + assert not empty2 + assert empty == empty2 + +def test_nodepath_single(): + """Tests NodePath behavior for single-node NodePaths.""" from panda3d.core import NodePath - empty = NodePath('np') + np = NodePath('np') + assert not np.is_empty() + assert np - assert empty.get_pos() == (0, 0, 0) - assert empty.get_hpr() == (0, 0, 0) - assert empty.get_scale() == (1, 1, 1) + assert np.get_pos() == (0, 0, 0) + assert np.get_hpr() == (0, 0, 0) + assert np.get_scale() == (1, 1, 1) def test_nodepath_parent(): """Tests NodePath.reparentTo().""" From a17ac6516c36a25d28d940cc13bcf9c1480f20f7 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 20:19:40 +0100 Subject: [PATCH 327/424] pgraph: fix regression in 89236ac1360e5f0249d425e61d537deef40e71c3 --- panda/src/pgraph/billboardEffect.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/pgraph/billboardEffect.cxx b/panda/src/pgraph/billboardEffect.cxx index 60c6e72c97..d6319021f1 100644 --- a/panda/src/pgraph/billboardEffect.cxx +++ b/panda/src/pgraph/billboardEffect.cxx @@ -374,6 +374,6 @@ fillin(DatagramIterator &scan, BamReader *manager) { if (manager->get_file_minor_ver() >= 43) { _look_at.fillin(scan, manager); - _fixed_depth = scan.get_stdfloat(); + _fixed_depth = scan.get_bool(); } } From a0af670dc7ad5dd31efaaf604aa27ea22e2e8110 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 20:26:54 +0100 Subject: [PATCH 328/424] makewheel: don't try to resolve deps with absolute path --- makepanda/makewheel.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 41d751281a..d53a51bc44 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -304,17 +304,23 @@ class WheelFile(object): # Temporary hack for 1.9, which had link deps on modules. return - source_path = None + if sys.platform == "darwin" and dep.startswith("/System/"): + return - if search_path is None: - search_path = self.lib_path + if dep.startswith('/'): + source_path = dep + else: + source_path = None - for lib_dir in search_path: - # Ignore static stuff. - path = os.path.join(lib_dir, dep) - if os.path.isfile(path): - source_path = os.path.normpath(path) - break + if search_path is None: + search_path = self.lib_path + + for lib_dir in search_path: + # Ignore static stuff. + path = os.path.join(lib_dir, dep) + if os.path.isfile(path): + source_path = os.path.normpath(path) + break if not source_path: # Couldn't find library in the panda3d lib dir. From e12ff1140bad4385c6351766a656c79cc17db945 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 20:29:19 +0100 Subject: [PATCH 329/424] makewheel: fix picking up libssl/libcrypto for _hashlib on macOS --- makepanda/makewheel.py | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index d53a51bc44..3e2cc0c06f 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -343,20 +343,12 @@ class WheelFile(object): if ext in ('.so', '.dylib') or '.so.' in os.path.basename(source_path) or \ (not ext and is_executable(source_path)): - # Scan and add Unix dependencies. + # Scan Unix dependencies. if target_path not in IGNORE_UNIX_DEPS_OF: deps = scan_dependencies(source_path) else: deps = [] - for dep in deps: - # Only include dependencies with relative path. Otherwise we - # end up overwriting system files like /lib/ld-linux.so.2! - # Yes, it happened to me. - if '/' not in dep: - target_dep = os.path.dirname(target_path) + '/' + dep - self.consider_add_dependency(target_dep, dep) - suffix = '' if '.so' in os.path.basename(source_path): suffix = '.so' @@ -382,7 +374,7 @@ class WheelFile(object): os.fchmod(temp.fileno(), os.fstat(temp.fileno()).st_mode | 0o111) temp.close() - # Fix things like @loader_path/../lib references + # Now add dependencies. On macOS, fix @loader_path references. if sys.platform == "darwin": if source_path.endswith('deploy-stubw'): deps_path = '@executable_path/../Frameworks' @@ -394,10 +386,8 @@ class WheelFile(object): # If this references the Python framework, change it # to reference libpython instead. new_dep = deps_path + '/libpython{0}.{1}.dylib'.format(*sys.version_info) - else: - if '@loader_path' not in dep: - continue + elif '@loader_path' in dep: dep_path = dep.replace('@loader_path', '.') target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep) target_dep = self.consider_add_dependency(target_dep, dep_path, loader_path) @@ -406,8 +396,30 @@ class WheelFile(object): continue new_dep = os.path.join(deps_path, os.path.relpath(target_dep, os.path.dirname(target_path))) + elif dep.startswith('/Library/Frameworks/Python.framework/'): + # Add this dependency if it's in the Python directory. + target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep) + target_dep = self.consider_add_dependency(target_dep, dep, loader_path) + if not target_dep: + # It won't be included, so no use adjusting the path. + continue + new_dep = os.path.join(deps_path, os.path.relpath(target_dep, os.path.dirname(target_path))) + + else: + if '/' in dep: + if GetVerbose(): + print("Ignoring dependency %s" % (dep)) + continue + subprocess.call(["install_name_tool", "-change", dep, new_dep, temp.name]) else: + # On other unixes, we just add dependencies normally. + for dep in deps: + # Only include dependencies with relative path, for now. + if '/' not in dep: + target_dep = os.path.dirname(target_path) + '/' + dep + self.consider_add_dependency(target_dep, dep) + subprocess.call(["strip", "-s", temp.name]) subprocess.call(["patchelf", "--set-rpath", "$ORIGIN", temp.name]) From 083e3ecc7453c9224d22c92ec5319bcad0477728 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 21:18:03 +0100 Subject: [PATCH 330/424] deploy-ng: add DLL search directory for scipy wheel --- direct/src/showutil/dist.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 2df47596c7..6829e27007 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -96,6 +96,14 @@ PACKAGE_DATA_DIRS = { ], } +# Some dependencies have extra directories that need to be scanned for DLLs. +# This dictionary maps wheel basenames (ie. the part of the .whl basename +# before the first hyphen) to a list of directories inside the .whl. + +PACKAGE_LIB_DIRS = { + 'scipy': ['scipy/extra-dll'], +} + # site.py for Python 2. SITE_PY2 = u""" import sys @@ -697,6 +705,13 @@ class build_apps(setuptools.Command): rootdir = wf.split(os.path.sep, 1)[0] search_path.append(os.path.join(whl, rootdir, '.libs')) + # Also look for more specific per-package cases, defined in + # PACKAGE_LIB_DIRS at the top of this file. + whl_name = os.path.basename(whl).split('-', 1)[0] + extra_dirs = PACKAGE_LIB_DIRS.get(whl_name, []) + for extra_dir in extra_dirs: + search_path.append(os.path.join(whl, extra_dir.replace('/', os.path.sep))) + target_path = os.path.join(builddir, basename) self.copy_with_dependencies(source_path, target_path, search_path) From 8a9cac8c4988470364d0bc3ecb56521aa3731c52 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 9 Dec 2018 21:24:51 +0100 Subject: [PATCH 331/424] deploy-ng: use proper patch matching for globs --- direct/src/showutil/dist.py | 30 +++++++++++++++++------------- samples/asteroids/setup.py | 6 +++--- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 6829e27007..f35916d067 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -248,11 +248,13 @@ class build_apps(setuptools.Command): 'libbz2.so.*', 'libz.so.*', 'liblzma.so.*', 'librt.so.*', 'libutil.so.*', # macOS - '*foundation.framework*', '*appkit.framework*', '*libstdc++.*.dylib', - '*libobjc.*.dylib', '*trustevaluationagent.framework*', '*libz.*.dylib', - '*coreservices.framework*', '*applicationservices.framework*', - '*opengl.framework*', '*carbon.framework*', '*cocoa.framework*', - '*libsystem.*.dylib', '*libedit.*.dylib' + '/usr/lib/libstdc++.*.dylib', + '/usr/lib/libz.*.dylib', + '/usr/lib/libobjc.*.dylib', + '/usr/lib/libSystem.*.dylib', + '/usr/lib/libbz2.*.dylib', + '/usr/lib/libedit.*.dylib', + '/System/Library/**', ] self.package_data_dirs = {} @@ -325,6 +327,8 @@ class build_apps(setuptools.Command): assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps' self.exclude_dependencies = [p3d.GlobPattern(i) for i in self.exclude_dependencies] + for glob in self.exclude_dependencies: + glob.case_sensitive = False tmp = self.default_file_handlers.copy() tmp.update(self.file_handlers) @@ -768,9 +772,9 @@ class build_apps(setuptools.Command): # Copy Game Files self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO) ignore_copy_list = [ - '__pycache__', - '*.pyc', - '{}/*'.format(self.build_base), + '**/__pycache__/**', + '**/*.pyc', + '{}/**'.format(self.build_base), ] ignore_copy_list += self.exclude_patterns ignore_copy_list += freezer_modpaths @@ -789,12 +793,12 @@ class build_apps(setuptools.Command): for pattern in pattern_list: # If the pattern is absolute, match against the absolute filename. if pattern.pattern[0] == '/': - #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches(abspath))) - if pattern.matches(abspath): + #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches_file(abspath))) + if pattern.matches_file(abspath): return True else: - #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches(path))) - if pattern.matches(path): + #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches_file(path))) + if pattern.matches_file(path): return True return False @@ -858,7 +862,7 @@ class build_apps(setuptools.Command): return for dep in self.exclude_dependencies: - if dep.matches(name.lower()): + if dep.matches_file(name): return for dir in search_path: diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index 955e68b947..95a3e5259f 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -5,9 +5,9 @@ setup( options = { 'build_apps': { 'include_patterns': [ - '*.png', - '*.jpg', - '*.egg', + '**/*.png', + '**/*.jpg', + '**/*.egg', ], 'gui_apps': { 'asteroids': 'main.py', From 147bac69a76817097ffc65bd01de3709ca7c6fb0 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 13 Dec 2018 09:30:05 +0100 Subject: [PATCH 332/424] travis: encrypt IRC channel name to prevent fork spam in IRC [skip ci] --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 64cc557d5b..2c4efe8a65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ script: notifications: irc: channels: - - "chat.freenode.net#panda3d" + - secure: "jfwHT9RHAVOGRGTMY8TpYKJI6rq8nFoIj41Y0soZdJQNWtSSFEK9AyzZeMY+2dHga7cR/X+/0NWZ2ehhedTnd9FvlzOnMWWC3K0I/b3XWbEdVEqIZnggFkKGqs82Gy3omguRC63yWupeJCcSCckIhoWbLzWy6xV8lF5WC80iXi8=" on_success: change on_failure: always use_notice: true From b11debcf3dcb7471533efb6c5fd8611e43904857 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Dec 2018 11:49:38 +0100 Subject: [PATCH 333/424] pfreeze: fixes for Python 3.7 See python/cpython#5432 - imp module now has the proper name The DL_IMPORT and DL_EXPORT macros seem to have been removed. --- direct/src/showutil/FreezeTool.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index aafbc24124..6114692092 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -42,10 +42,11 @@ builtinInitFuncs = { '__builtin__': None, 'sys': None, 'exceptions': None, - '_imp': 'PyInit_imp', '_warnings': '_PyWarnings_Init', 'marshal': 'PyMarshal_Init', } +if sys.version_info < (3, 7): + builtinInitFuncs['_imp'] = 'PyInit_imp' # These are missing modules that we've reported already this session. reportedMissing = {} @@ -256,7 +257,7 @@ frozenMainCode = """ extern void PyWinFreeze_ExeInit(void); extern void PyWinFreeze_ExeTerm(void); -extern DL_IMPORT(int) PyImport_ExtendInittab(struct _inittab *newtab); +extern PyAPI_FUNC(int) PyImport_ExtendInittab(struct _inittab *newtab); #endif /* Main program */ @@ -1394,7 +1395,7 @@ class Freezer: libName = module.split('.')[-1] initFunc = builtinInitFuncs.get(module, 'PyInit_' + libName) if initFunc: - text += 'extern DL_IMPORT(PyObject) *%s(void);\n' % (initFunc) + text += 'extern PyAPI_FUNC(PyObject) *%s(void);\n' % (initFunc) text += '\n' if sys.platform == "win32": @@ -1417,7 +1418,7 @@ class Freezer: libName = module.split('.')[-1] initFunc = builtinInitFuncs.get(module, 'init' + libName) if initFunc: - text += 'extern DL_IMPORT(void) %s(void);\n' % (initFunc) + text += 'extern PyAPI_FUNC(void) %s(void);\n' % (initFunc) text += '\n' if sys.platform == "win32": From 3a3cb01148820ffd396a5903dbe5d4a680684372 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Dec 2018 18:43:31 +0100 Subject: [PATCH 334/424] showbase: return Transitions fade to aspect2d, fix focus issue This implements a slightly different solution for #311 that maintains the behaviour for the fade screen to prevent clicks from propagating to the geometry below it. Fixes #475 Fixes #311 --- direct/src/showbase/Transitions.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/direct/src/showbase/Transitions.py b/direct/src/showbase/Transitions.py index 9e05daad3e..54d7bd2baa 100644 --- a/direct/src/showbase/Transitions.py +++ b/direct/src/showbase/Transitions.py @@ -89,6 +89,8 @@ class Transitions: self.fade.setBin('unsorted', 0) self.fade.setColor(0,0,0,0) + self.fade.setScale(max(base.a2dRight, base.a2dTop)) + def getFadeInIval(self, t=0.5, finishIval=None, blendType='noBlend'): """ Returns an interval without starting it. This is particularly useful in @@ -97,8 +99,7 @@ class Transitions: #self.noTransitions() masad: this creates a one frame pop, is it necessary? self.loadFade() - parent = aspect2d if self.fadeModel else render2d - transitionIval = Sequence(Func(self.fade.reparentTo, parent, DGG.FADE_SORT_INDEX), + transitionIval = Sequence(Func(self.fade.reparentTo, aspect2d, DGG.FADE_SORT_INDEX), Func(self.fade.showThrough), # in case aspect2d is hidden for some reason self.lerpFunc(self.fade, t, self.alphaOff, @@ -120,8 +121,7 @@ class Transitions: self.noTransitions() self.loadFade() - parent = aspect2d if self.fadeModel else render2d - transitionIval = Sequence(Func(self.fade.reparentTo, parent, DGG.FADE_SORT_INDEX), + transitionIval = Sequence(Func(self.fade.reparentTo, aspect2d, DGG.FADE_SORT_INDEX), Func(self.fade.showThrough), # in case aspect2d is hidden for some reason self.lerpFunc(self.fade, t, self.alphaOn, @@ -181,8 +181,7 @@ class Transitions: self.noTransitions() self.loadFade() - parent = aspect2d if self.fadeModel else render2d - self.fade.reparentTo(parent, DGG.FADE_SORT_INDEX) + self.fade.reparentTo(aspect2d, DGG.FADE_SORT_INDEX) self.fade.setColor(self.alphaOn) elif ConfigVariableBool('no-loading-screen', False): if finishIval: @@ -215,8 +214,7 @@ class Transitions: self.noTransitions() self.loadFade() - parent = aspect2d if self.fadeModel else render2d - self.fade.reparentTo(parent, DGG.FADE_SORT_INDEX) + self.fade.reparentTo(aspect2d, DGG.FADE_SORT_INDEX) self.fade.setColor(self.alphaOn[0], self.alphaOn[1], self.alphaOn[2], @@ -232,8 +230,7 @@ class Transitions: self.noTransitions() self.loadFade() - parent = aspect2d if self.fadeModel else render2d - self.fade.reparentTo(parent, DGG.FADE_SORT_INDEX) + self.fade.reparentTo(aspect2d, DGG.FADE_SORT_INDEX) self.fade.setColor(color) def noFade(self): From 7995d483abbb84af56d22b20adab7ab3cbe1fd1c Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Dec 2018 18:46:11 +0100 Subject: [PATCH 335/424] py_panda: make standard_type_members static and internal --- dtool/src/interrogatedb/dtool_super_base.cxx | 10 ++++++++++ dtool/src/interrogatedb/py_panda.cxx | 10 ---------- dtool/src/interrogatedb/py_panda.h | 3 --- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/dtool/src/interrogatedb/dtool_super_base.cxx b/dtool/src/interrogatedb/dtool_super_base.cxx index 01dcdef077..9422084299 100644 --- a/dtool/src/interrogatedb/dtool_super_base.cxx +++ b/dtool/src/interrogatedb/dtool_super_base.cxx @@ -15,6 +15,16 @@ #ifdef HAVE_PYTHON +static PyMemberDef standard_type_members[] = { + {(char *)"this", (sizeof(void*) == sizeof(int)) ? T_UINT : T_ULONGLONG, offsetof(Dtool_PyInstDef, _ptr_to_object), READONLY, (char *)"C++ 'this' pointer, if any"}, + {(char *)"this_ownership", T_BOOL, offsetof(Dtool_PyInstDef, _memory_rules), READONLY, (char *)"C++ 'this' ownership rules"}, + {(char *)"this_const", T_BOOL, offsetof(Dtool_PyInstDef, _is_const), READONLY, (char *)"C++ 'this' const flag"}, +// {(char *)"this_signature", T_INT, offsetof(Dtool_PyInstDef, _signature), +// READONLY, (char *)"A type check signature"}, + {(char *)"this_metatype", T_OBJECT, offsetof(Dtool_PyInstDef, _My_Type), READONLY, (char *)"The dtool meta object"}, + {nullptr} /* Sentinel */ +}; + static PyObject *GetSuperBase(PyObject *self) { Dtool_PyTypedObject *super_base = Dtool_GetSuperBase(); Py_XINCREF((PyTypeObject *)super_base); // order is important .. this is used for static functions diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index bb7b5717f4..0c8b2416d7 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/dtool/src/interrogatedb/py_panda.cxx @@ -12,16 +12,6 @@ using std::string; -PyMemberDef standard_type_members[] = { - {(char *)"this", (sizeof(void*) == sizeof(int)) ? T_UINT : T_ULONGLONG, offsetof(Dtool_PyInstDef, _ptr_to_object), READONLY, (char *)"C++ 'this' pointer, if any"}, - {(char *)"this_ownership", T_BOOL, offsetof(Dtool_PyInstDef, _memory_rules), READONLY, (char *)"C++ 'this' ownership rules"}, - {(char *)"this_const", T_BOOL, offsetof(Dtool_PyInstDef, _is_const), READONLY, (char *)"C++ 'this' const flag"}, -// {(char *)"this_signature", T_INT, offsetof(Dtool_PyInstDef, _signature), -// READONLY, (char *)"A type check signature"}, - {(char *)"this_metatype", T_OBJECT, offsetof(Dtool_PyInstDef, _My_Type), READONLY, (char *)"The dtool meta object"}, - {nullptr} /* Sentinel */ -}; - /** */ diff --git a/dtool/src/interrogatedb/py_panda.h b/dtool/src/interrogatedb/py_panda.h index fa7981697b..ec2765e77a 100644 --- a/dtool/src/interrogatedb/py_panda.h +++ b/dtool/src/interrogatedb/py_panda.h @@ -67,9 +67,6 @@ struct Dtool_PyInstDef { bool _is_const; }; -// A Offset Dictionary Defining How to read the Above Object.. -extern PyMemberDef standard_type_members[]; - // The Class Definition Structor For a Dtool python type. struct Dtool_PyTypedObject { // Standard Python Features.. From 73452957eee582a882b9b03d97c8dd1a9bd4ed8f Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Dec 2018 20:36:22 +0100 Subject: [PATCH 336/424] gobj: better handle NaNs in vertex data when calculating bounds --- panda/src/gobj/geom.cxx | 12 ++--- panda/src/gobj/geomPrimitive.cxx | 76 +++++++++++++++++++------------- tests/gobj/test_geom.py | 42 ++++++++++++++++++ 3 files changed, 94 insertions(+), 36 deletions(-) diff --git a/panda/src/gobj/geom.cxx b/panda/src/gobj/geom.cxx index 8bede7327b..3b6dee026a 100644 --- a/panda/src/gobj/geom.cxx +++ b/panda/src/gobj/geom.cxx @@ -1338,7 +1338,7 @@ compute_internal_bounds(Geom::CData *cdata, Thread *current_thread) const { // Now actually compute the bounding volume. We do this by using // calc_tight_bounds to determine our box first. LPoint3 pmin, pmax; - PN_stdfloat sq_center_dist; + PN_stdfloat sq_center_dist = 0.0f; bool found_any = false; do_calc_tight_bounds(pmin, pmax, sq_center_dist, found_any, vertex_data, false, LMatrix4::ident_mat(), @@ -1379,7 +1379,7 @@ compute_internal_bounds(Geom::CData *cdata, Thread *current_thread) const { LPoint3 aabb_center = (pmin + pmax) * 0.5f; PN_stdfloat best_sq_radius = (pmax - aabb_center).length_squared(); - if (btype != BoundingVolume::BT_fastest && + if (btype != BoundingVolume::BT_fastest && best_sq_radius > 0.0f && aabb_center.length_squared() / best_sq_radius >= (0.2f * 0.2f)) { // Hmm, this is an off-center model. Maybe we can do a better job // by calculating the bounding sphere from the AABB center. @@ -1389,7 +1389,8 @@ compute_internal_bounds(Geom::CData *cdata, Thread *current_thread) const { do_calc_sphere_radius(aabb_center, better_sq_radius, found_any, vertex_data, cdata, current_thread); - if (found_any && better_sq_radius <= best_sq_radius) { + if (found_any && better_sq_radius > 0.0f && + better_sq_radius <= best_sq_radius) { // Great. This is as good a sphere as we're going to get. if (btype == BoundingVolume::BT_best && avg_box_area < better_sq_radius * MathNumbers::pi) { @@ -1409,7 +1410,7 @@ compute_internal_bounds(Geom::CData *cdata, Thread *current_thread) const { cdata->_internal_bounds = new BoundingBox(pmin, pmax); break; - } else if (sq_center_dist <= best_sq_radius) { + } else if (sq_center_dist >= 0.0f && sq_center_dist <= best_sq_radius) { // No, but a sphere centered on the origin is apparently still // better than a sphere around the bounding box. cdata->_internal_bounds = @@ -1420,7 +1421,8 @@ compute_internal_bounds(Geom::CData *cdata, Thread *current_thread) const { // This is the worst sphere we can make, which is why we will only // do it when the user specifically requests a sphere. cdata->_internal_bounds = - new BoundingSphere(aabb_center, csqrt(best_sq_radius)); + new BoundingSphere(aabb_center, + (best_sq_radius > 0.0f) ? csqrt(best_sq_radius) : 0.0f); break; } } diff --git a/panda/src/gobj/geomPrimitive.cxx b/panda/src/gobj/geomPrimitive.cxx index 2cd5e2739a..6fa66329ba 100644 --- a/panda/src/gobj/geomPrimitive.cxx +++ b/panda/src/gobj/geomPrimitive.cxx @@ -1607,13 +1607,16 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, } if (got_mat) { - if (!found_any) { - reader.set_row_unsafe(cdata->_first_vertex); + // Find the first non-NaN vertex. + while (!found_any && i < cdata->_num_vertices) { + reader.set_row(cdata->_first_vertex + i); LPoint3 first_vertex = mat.xform_point(reader.get_data3()); - min_point = first_vertex; - max_point = first_vertex; - sq_center_dist = first_vertex.length_squared(); - found_any = true; + if (!first_vertex.is_nan()) { + min_point = first_vertex; + max_point = first_vertex; + sq_center_dist = first_vertex.length_squared(); + found_any = true; + } ++i; } @@ -1630,13 +1633,16 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, sq_center_dist = max(sq_center_dist, vertex.length_squared()); } } else { - if (!found_any) { - reader.set_row_unsafe(cdata->_first_vertex); - const LVecBase3 &first_vertex = reader.get_data3(); - min_point = first_vertex; - max_point = first_vertex; - sq_center_dist = first_vertex.length_squared(); - found_any = true; + // Find the first non-NaN vertex. + while (!found_any && i < cdata->_num_vertices) { + reader.set_row(cdata->_first_vertex + i); + LPoint3 first_vertex = reader.get_data3(); + if (!first_vertex.is_nan()) { + min_point = first_vertex; + max_point = first_vertex; + sq_center_dist = first_vertex.length_squared(); + found_any = true; + } ++i; } @@ -1664,15 +1670,19 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, int strip_cut_index = get_strip_cut_index(cdata->_index_type); if (got_mat) { - if (!found_any) { - int first_index = index.get_data1i(); - nassertv(first_index != strip_cut_index); - reader.set_row_unsafe(first_index); - LPoint3 first_vertex = mat.xform_point(reader.get_data3()); - min_point = first_vertex; - max_point = first_vertex; - sq_center_dist = first_vertex.length_squared(); - found_any = true; + // Find the first non-NaN vertex. + while (!found_any && !index.is_at_end()) { + int ii = index.get_data1i(); + if (ii != strip_cut_index) { + reader.set_row(ii); + LPoint3 first_vertex = mat.xform_point(reader.get_data3()); + if (!first_vertex.is_nan()) { + min_point = first_vertex; + max_point = first_vertex; + sq_center_dist = first_vertex.length_squared(); + found_any = true; + } + } } while (!index.is_at_end()) { @@ -1692,15 +1702,19 @@ calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, sq_center_dist = max(sq_center_dist, vertex.length_squared()); } } else { - if (!found_any) { - int first_index = index.get_data1i(); - nassertv(first_index != strip_cut_index); - reader.set_row_unsafe(first_index); - const LVecBase3 &first_vertex = reader.get_data3(); - min_point = first_vertex; - max_point = first_vertex; - sq_center_dist = first_vertex.length_squared(); - found_any = true; + // Find the first non-NaN vertex. + while (!found_any && !index.is_at_end()) { + int ii = index.get_data1i(); + if (ii != strip_cut_index) { + reader.set_row(ii); + LVecBase3 first_vertex = reader.get_data3(); + if (!first_vertex.is_nan()) { + min_point = first_vertex; + max_point = first_vertex; + sq_center_dist = first_vertex.length_squared(); + found_any = true; + } + } } while (!index.is_at_end()) { diff --git a/tests/gobj/test_geom.py b/tests/gobj/test_geom.py index 2a5a462659..24eeab852f 100644 --- a/tests/gobj/test_geom.py +++ b/tests/gobj/test_geom.py @@ -40,3 +40,45 @@ def test_geom_decompose(): # Old primitive should still be unchanged assert prim == geom.get_primitive(0) + + +def test_geom_calc_sphere_bounds(): + # Ensure that it ignores NaN + data = core.GeomVertexData("", core.GeomVertexFormat.get_v3(), core.Geom.UH_static) + vertex = core.GeomVertexWriter(data, "vertex") + vertex.add_data3((float("NaN"), 0, 0)) + vertex.add_data3((1, 1, 1)) + vertex.add_data3((1, 1, 2)) + + prim = core.GeomPoints(core.Geom.UH_static) + prim.add_next_vertices(3) + + geom = core.Geom(data) + geom.add_primitive(prim) + geom.set_bounds_type(core.BoundingVolume.BT_sphere) + + bounds = geom.get_bounds() + assert isinstance(bounds, core.BoundingSphere) + assert bounds.get_center() == (1, 1, 1.5) + assert bounds.get_radius() == 0.5 + + +def test_geom_calc_box_bounds(): + # Ensure that it ignores NaN + data = core.GeomVertexData("", core.GeomVertexFormat.get_v3(), core.Geom.UH_static) + vertex = core.GeomVertexWriter(data, "vertex") + vertex.add_data3((float("NaN"), 0, 0)) + vertex.add_data3((1, 1, 1)) + vertex.add_data3((1, 1, 2)) + + prim = core.GeomPoints(core.Geom.UH_static) + prim.add_next_vertices(3) + + geom = core.Geom(data) + geom.add_primitive(prim) + geom.set_bounds_type(core.BoundingVolume.BT_box) + + bounds = geom.get_bounds() + assert isinstance(bounds, core.BoundingBox) + assert bounds.get_min() == (1, 1, 1) + assert bounds.get_max() == (1, 1, 2) From 65a86d1b3fa5127acd345d0d2b5b29a6e4a0fced Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Dec 2018 21:01:38 +0100 Subject: [PATCH 337/424] shader: warn if GLSL shader does not contain #version line Shaders without #version line (which are supposed to indicate GLSL 1.10) will be interpreted differently by different drivers, so this is almost certainly a mistake. In the future, we can forbid this altogether or insert #version 110 automatically. --- panda/src/gobj/shader.cxx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/panda/src/gobj/shader.cxx b/panda/src/gobj/shader.cxx index 4a1092eb9c..fde084aa18 100644 --- a/panda/src/gobj/shader.cxx +++ b/panda/src/gobj/shader.cxx @@ -2750,6 +2750,7 @@ r_preprocess_source(ostream &out, istream &in, const Filename &fn, int ext_google_include = 0; // 1 = warn, 2 = enable int ext_google_line = 0; bool had_include = false; + bool had_version = false; int lineno = 0; bool write_line_directive = (fileno != 0); @@ -2920,6 +2921,9 @@ r_preprocess_source(ostream &out, istream &in, const Filename &fn, write_line_directive = true; } + } else if (strcmp(directive, "version") == 0) { + had_version = true; + } else if (strcmp(directive, "extension") == 0) { // Check for special preprocessing extensions. char extension[256]; @@ -3047,6 +3051,11 @@ r_preprocess_source(ostream &out, istream &in, const Filename &fn, out << line << "\n"; } + if (fileno == 0 && !had_version) { + shader_cat.warning() + << "GLSL shader " << fn << " does not contain a #version line!\n"; + } + return true; } From 6b8814f9c9d341d2a43f4f25f44481c136650846 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Dec 2018 21:03:43 +0100 Subject: [PATCH 338/424] mathutil: add some properties for bounding volumes --- panda/src/mathutil/boundingSphere.h | 2 ++ panda/src/mathutil/finiteBoundingVolume.h | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/panda/src/mathutil/boundingSphere.h b/panda/src/mathutil/boundingSphere.h index 8c105daa01..2e63fb3396 100644 --- a/panda/src/mathutil/boundingSphere.h +++ b/panda/src/mathutil/boundingSphere.h @@ -47,6 +47,8 @@ PUBLISHED: INLINE_MATHUTIL void set_center(const LPoint3 ¢er); INLINE_MATHUTIL void set_radius(PN_stdfloat radius); + MAKE_PROPERTY(center, get_center, set_center); + MAKE_PROPERTY(radius, get_radius, set_radius); public: virtual const BoundingSphere *as_bounding_sphere() const; diff --git a/panda/src/mathutil/finiteBoundingVolume.h b/panda/src/mathutil/finiteBoundingVolume.h index e344151685..e527faa88b 100644 --- a/panda/src/mathutil/finiteBoundingVolume.h +++ b/panda/src/mathutil/finiteBoundingVolume.h @@ -30,6 +30,10 @@ PUBLISHED: virtual LPoint3 get_max() const=0; virtual PN_stdfloat get_volume() const; + MAKE_PROPERTY(min, get_min); + MAKE_PROPERTY(max, get_max); + MAKE_PROPERTY(volume, get_volume); + public: virtual const FiniteBoundingVolume *as_finite_bounding_volume() const; From c0138930c9f7abf0b7048baa20912137c36afa71 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 16 Dec 2018 22:36:54 +0100 Subject: [PATCH 339/424] makewheel: properly chmod libraries that have no write permissions Seems to be necessary on macOS for install_name_tool to be able to work on some libraries. Not sure why fchmod/fstat isn't working right. --- makepanda/makewheel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 3e2cc0c06f..29affea238 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -371,8 +371,8 @@ class WheelFile(object): # Otherwise, just copy it over. temp.write(open(source_path, 'rb').read()) - os.fchmod(temp.fileno(), os.fstat(temp.fileno()).st_mode | 0o111) temp.close() + os.chmod(temp.name, os.stat(temp.name).st_mode | 0o711) # Now add dependencies. On macOS, fix @loader_path references. if sys.platform == "darwin": From b7d638e53d03046527fe7323bf383c2ddb44d3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derzsi=20D=C3=A1niel?= Date: Tue, 18 Dec 2018 16:49:00 +0200 Subject: [PATCH 340/424] pgraph: fix BillboardEffect regression with older models In older BAM files (version <= 6.42), _fixed_depth will have an uncertain value after loading, leading to incorrect transform calculations, which cause rendering issues (see issue #474) --- panda/src/pgraph/billboardEffect.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panda/src/pgraph/billboardEffect.cxx b/panda/src/pgraph/billboardEffect.cxx index d6319021f1..ff44e11202 100644 --- a/panda/src/pgraph/billboardEffect.cxx +++ b/panda/src/pgraph/billboardEffect.cxx @@ -375,5 +375,7 @@ fillin(DatagramIterator &scan, BamReader *manager) { if (manager->get_file_minor_ver() >= 43) { _look_at.fillin(scan, manager); _fixed_depth = scan.get_bool(); + } else { + _fixed_depth = false; } } From 11526852edb23de2b42ff18371ecc05b02abe45d Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 18 Dec 2018 19:59:36 +0100 Subject: [PATCH 341/424] glgsg: add ability to get red/rg-only floating-point renderbuffers --- panda/src/glstuff/glGraphicsBuffer_src.cxx | 37 ++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/panda/src/glstuff/glGraphicsBuffer_src.cxx b/panda/src/glstuff/glGraphicsBuffer_src.cxx index 06c26a590d..8fbba8f79d 100644 --- a/panda/src/glstuff/glGraphicsBuffer_src.cxx +++ b/panda/src/glstuff/glGraphicsBuffer_src.cxx @@ -921,20 +921,37 @@ bind_slot(int layer, bool rb_resize, Texture **attach, RenderTexturePlane slot, if (_fb_properties.get_alpha_bits() == 0) { if (_fb_properties.get_srgb_color()) { gl_format = GL_SRGB8; + } else if (_fb_properties.get_color_bits() > 16 * 3 || + _fb_properties.get_red_bits() > 16 || + _fb_properties.get_green_bits() > 16 || + _fb_properties.get_blue_bits() > 16) { + // 32-bit, which is always floating-point. + if (_fb_properties.get_blue_bits() > 0 || + _fb_properties.get_color_bits() == 1 || + _fb_properties.get_color_bits() > 32 * 2) { + gl_format = GL_RGB32F; + } else if (_fb_properties.get_green_bits() > 0 || + _fb_properties.get_color_bits() > 32) { + gl_format = GL_RG32F; + } else { + gl_format = GL_R32F; + } } else if (_fb_properties.get_float_color()) { - if (_fb_properties.get_color_bits() > 16 * 3) { - gl_format = GL_RGB32F_ARB; + // 16-bit floating-point. + if (_fb_properties.get_blue_bits() > 0 || + _fb_properties.get_color_bits() == 1 || + _fb_properties.get_color_bits() > 16 * 2) { + gl_format = GL_RGB16F; + } else if (_fb_properties.get_green_bits() > 0 || + _fb_properties.get_color_bits() > 16) { + gl_format = GL_RG16F; } else { - gl_format = GL_RGB16F_ARB; + gl_format = GL_R16F; } + } else if (_fb_properties.get_color_bits() > 8 * 3) { + gl_format = GL_RGB16_EXT; } else { - if (_fb_properties.get_color_bits() > 16 * 3) { - gl_format = GL_RGB32F_ARB; - } else if (_fb_properties.get_color_bits() > 8 * 3) { - gl_format = GL_RGB16_EXT; - } else { - gl_format = GL_RGB; - } + gl_format = GL_RGB; } } else { if (_fb_properties.get_srgb_color()) { From d98301b8d322ed5bbebcdb66d0a47f256e280aec Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 18 Dec 2018 19:03:08 -0800 Subject: [PATCH 342/424] deploy-ng: Add direct.particles.ParticleManagerGlobal as hidden import for direct.particles Fixes #481 --- direct/src/showutil/FreezeTool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index 7ae33f9ac5..f014a7bd9e 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -75,6 +75,7 @@ hiddenImports = { 'datetime': ['_strptime'], 'keyring.backends': ['keyring.backends.*'], 'matplotlib.font_manager': ['encodings.mac_roman'], + 'direct.particles': ['direct.particles.ParticleManagerGlobal'], } if sys.version_info >= (3,): From 8dcb735f2b9e196394c119a7d95568dedd7af58f Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 18 Dec 2018 19:43:37 -0800 Subject: [PATCH 343/424] x11display: Release M_relative mouse when window loses focus Fixes #480 --- panda/src/x11display/x11GraphicsWindow.cxx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index ad0f8e44dd..be28a6254f 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -508,23 +508,33 @@ process_events() { changed_properties = true; } - if (properties.has_foreground() && _properties.get_mouse_mode() == WindowProperties::M_confined) { + if (properties.has_foreground() && ( + _properties.get_mouse_mode() == WindowProperties::M_confined || + _dga_mouse_enabled)) { + x11GraphicsPipe *x11_pipe; + DCAST_INTO_V(x11_pipe, _pipe); + // Focus has changed, let's let go of the pointer if we've grabbed or re-grab it if needed if (properties.get_foreground()) { // Window is going to the foreground, re-grab the pointer X11_Cursor cursor = None; if (_properties.get_cursor_hidden()) { - x11GraphicsPipe *x11_pipe; - DCAST_INTO_V(x11_pipe, _pipe); cursor = x11_pipe->get_hidden_cursor(); } XGrabPointer(_display, _xwindow, True, 0, GrabModeAsync, GrabModeAsync, _xwindow, cursor, CurrentTime); + if (_dga_mouse_enabled) { + x11_pipe->enable_relative_mouse(); + } } else { // window is leaving the foreground, ungrab the pointer - XUngrabPointer(_display, CurrentTime); + if (_dga_mouse_enabled) { + x11_pipe->disable_relative_mouse(); + } else if (_properties.get_mouse_mode() == WindowProperties::M_confined) { + XUngrabPointer(_display, CurrentTime); + } } } From 587237577fbd41f8c442d787d1445737236f7baf Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 18 Dec 2018 20:33:12 -0800 Subject: [PATCH 344/424] deploy-ng: Move distribution related files from direct.showtutil to direct.dist --- direct/src/{showutil => dist}/FreezeTool.py | 0 direct/src/{showutil/dist.py => dist/commands.py} | 0 direct/src/{showutil => dist}/pefile.py | 0 direct/src/{showutil => dist}/pfreeze.py | 2 +- direct/src/p3d/Packager.py | 2 +- makepanda/makepanda.py | 6 +++--- makepanda/makewheel.py | 6 +++--- makepanda/test_imports.py | 2 +- panda/src/iphone/ipfreeze.py | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) rename direct/src/{showutil => dist}/FreezeTool.py (100%) rename direct/src/{showutil/dist.py => dist/commands.py} (100%) rename direct/src/{showutil => dist}/pefile.py (100%) rename direct/src/{showutil => dist}/pfreeze.py (99%) diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/dist/FreezeTool.py similarity index 100% rename from direct/src/showutil/FreezeTool.py rename to direct/src/dist/FreezeTool.py diff --git a/direct/src/showutil/dist.py b/direct/src/dist/commands.py similarity index 100% rename from direct/src/showutil/dist.py rename to direct/src/dist/commands.py diff --git a/direct/src/showutil/pefile.py b/direct/src/dist/pefile.py similarity index 100% rename from direct/src/showutil/pefile.py rename to direct/src/dist/pefile.py diff --git a/direct/src/showutil/pfreeze.py b/direct/src/dist/pfreeze.py similarity index 99% rename from direct/src/showutil/pfreeze.py rename to direct/src/dist/pfreeze.py index 2c2827e051..112dbcca0b 100755 --- a/direct/src/showutil/pfreeze.py +++ b/direct/src/dist/pfreeze.py @@ -59,7 +59,7 @@ Options:: import getopt import sys import os -from direct.showutil import FreezeTool +from . import FreezeTool def usage(code, msg = ''): if __doc__: diff --git a/direct/src/p3d/Packager.py b/direct/src/p3d/Packager.py index 784055b771..4e8a303f7f 100644 --- a/direct/src/p3d/Packager.py +++ b/direct/src/p3d/Packager.py @@ -19,7 +19,7 @@ from direct.p3d.SeqValue import SeqValue from direct.p3d.HostInfo import HostInfo from direct.showbase import Loader from direct.showbase import AppRunnerGlobal -from direct.showutil import FreezeTool +from direct.dist import FreezeTool from direct.directnotify.DirectNotifyGlobal import * vfs = VirtualFileSystem.getGlobalPtr() diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 5bed398fef..1c13afb03f 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2061,7 +2061,7 @@ def FreezePy(target, inputs, opts): if sys.version_info >= (2, 6): cmdstr += "-B " - cmdstr += os.path.join(GetOutputDir(), "direct", "showutil", "pfreeze.py") + cmdstr += os.path.join(GetOutputDir(), "direct", "dist", "pfreeze.py") if 'FREEZE_STARTUP' in opts: cmdstr += " -s" @@ -2992,8 +2992,8 @@ Author-email: etc-panda3d@lists.andrew.cmu.edu """ ENTRY_POINTS = """[distutils.commands] -build_apps = direct.showutil.dist:build_apps -bdist_apps = direct.showutil.dist:bdist_apps +build_apps = direct.dist.commands:build_apps +bdist_apps = direct.dist.commands:bdist_apps """ if not PkgSkip("DIRECT"): diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 29affea238..18f92379b8 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -643,7 +643,7 @@ __version__ = '{0}' # Add a panda3d-tools directory containing the executables. entry_points = '[console_scripts]\n' entry_points += 'eggcacher = direct.directscripts.eggcacher:main\n' - entry_points += 'pfreeze = direct.showutil.pfreeze:main\n' + entry_points += 'pfreeze = direct.dist.pfreeze:main\n' tools_init = '' for file in os.listdir(bin_dir): basename = os.path.splitext(file)[0] @@ -661,8 +661,8 @@ __version__ = '{0}' entry_points += '{0} = panda3d_tools:{1}\n'.format(basename, funcname) tools_init += '{0} = lambda: _exec_tool({1!r})\n'.format(funcname, file) entry_points += '[distutils.commands]\n' - entry_points += 'build_apps = direct.showutil.dist:build_apps\n' - entry_points += 'bdist_apps = direct.showutil.dist:bdist_apps\n' + entry_points += 'build_apps = direct.dist.commands:build_apps\n' + entry_points += 'bdist_apps = direct.dist.commands:bdist_apps\n' whl.write_file_data('panda3d_tools/__init__.py', PANDA3D_TOOLS_INIT.format(tools_init)) diff --git a/makepanda/test_imports.py b/makepanda/test_imports.py index ecd87cbb66..a9b22943c8 100644 --- a/makepanda/test_imports.py +++ b/makepanda/test_imports.py @@ -62,6 +62,7 @@ import direct.directutil.LargeBlobSenderConsts import direct.directutil.Mopath import direct.directutil.Verify import direct.directutil.WeightedChoice +import direct.dist.FreezeTool import direct.distributed.AsyncRequest import direct.distributed.CRCache import direct.distributed.CRDataCache @@ -234,7 +235,6 @@ import direct.showbase.VFSImporter import direct.showbase.WxGlobal import direct.showutil.BuildGeometry import direct.showutil.Effects -import direct.showutil.FreezeTool import direct.showutil.Rope import direct.showutil.TexMemWatcher import direct.showutil.TexViewer diff --git a/panda/src/iphone/ipfreeze.py b/panda/src/iphone/ipfreeze.py index 5a7462eb9e..d7fe343a15 100755 --- a/panda/src/iphone/ipfreeze.py +++ b/panda/src/iphone/ipfreeze.py @@ -14,7 +14,7 @@ Usage: import getopt import sys import os -from direct.showutil import FreezeTool +from direct.dist import FreezeTool def usage(code, msg = ''): print >> sys.stderr, __doc__ From 840f28ef182b703d08dcb028dd3809b20846ffae Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 19 Dec 2018 12:43:39 +0100 Subject: [PATCH 345/424] glgsg: fix framebuffer_copy_to_ram format for float framebuffers --- .../src/glstuff/glGraphicsStateGuardian_src.cxx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 7974a9345c..df2095284c 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -7049,6 +7049,16 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z, } else { format = Texture::F_srgb; } + } else if (_current_properties->get_float_color()) { + if (_current_properties->get_alpha_bits()) { + format = Texture::F_rgba32; + } else if (_current_properties->get_blue_bits()) { + format = Texture::F_rgb32; + } else if (_current_properties->get_green_bits()) { + format = Texture::F_rg32; + } else { + format = Texture::F_r32; + } } else { if (_current_properties->get_alpha_bits()) { format = Texture::F_rgba; @@ -7058,7 +7068,11 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z, } if (_current_properties->get_float_color()) { component_type = Texture::T_float; - } else if (_current_properties->get_color_bits() <= 24) { + } else if (_current_properties->get_color_bits() <= 24 + && _current_properties->get_red_bits() <= 8 + && _current_properties->get_green_bits() <= 8 + && _current_properties->get_blue_bits() <= 8 + && _current_properties->get_alpha_bits() <= 8) { component_type = Texture::T_unsigned_byte; } else { component_type = Texture::T_unsigned_short; From e4f493b872c16f6b242db29a654db2c898f06fb3 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 19 Dec 2018 12:45:32 +0100 Subject: [PATCH 346/424] dcparser: use vector_uchar for blobs, step towards Python 3 compat Closes #298 --- direct/src/dcparser/dcArrayParameter.cxx | 72 + direct/src/dcparser/dcArrayParameter.h | 4 + direct/src/dcparser/dcAtomicField.cxx | 10 +- direct/src/dcparser/dcAtomicField.h | 2 +- direct/src/dcparser/dcClass.cxx | 11 +- direct/src/dcparser/dcClass.h | 2 +- direct/src/dcparser/dcField.I | 6 +- direct/src/dcparser/dcField.cxx | 19 +- direct/src/dcparser/dcField.h | 12 +- direct/src/dcparser/dcLexer.cxx.prebuilt | 378 +-- direct/src/dcparser/dcLexer.lxx | 62 +- direct/src/dcparser/dcPacker.I | 120 +- direct/src/dcparser/dcPacker.cxx | 18 +- direct/src/dcparser/dcPacker.h | 19 +- direct/src/dcparser/dcPackerInterface.cxx | 16 + direct/src/dcparser/dcPackerInterface.h | 9 +- direct/src/dcparser/dcParser.cxx.prebuilt | 2864 ++++++++--------- direct/src/dcparser/dcParser.h.prebuilt | 129 +- direct/src/dcparser/dcParser.yxx | 55 +- direct/src/dcparser/dcParserDefs.h | 2 + direct/src/dcparser/dcSimpleParameter.cxx | 119 + direct/src/dcparser/dcSimpleParameter.h | 4 + direct/src/dcparser/dcSwitch.cxx | 15 +- direct/src/dcparser/dcSwitch.h | 12 +- direct/src/dcparser/hashGenerator.cxx | 12 + direct/src/dcparser/hashGenerator.h | 2 + .../src/distributed/cConnectionRepository.cxx | 2 +- 27 files changed, 2016 insertions(+), 1960 deletions(-) diff --git a/direct/src/dcparser/dcArrayParameter.cxx b/direct/src/dcparser/dcArrayParameter.cxx index 2f2ea69932..c2c73635a2 100644 --- a/direct/src/dcparser/dcArrayParameter.cxx +++ b/direct/src/dcparser/dcArrayParameter.cxx @@ -262,6 +262,38 @@ pack_string(DCPackData &pack_data, const string &value, } } +/** + * Packs the indicated numeric or string value into the stream. + */ +void DCArrayParameter:: +pack_blob(DCPackData &pack_data, const vector_uchar &value, + bool &pack_error, bool &range_error) const { + // We can only pack a string if the array element type is char or int8. + DCSimpleParameter *simple_type = _element_type->as_simple_parameter(); + if (simple_type == nullptr) { + pack_error = true; + return; + } + + size_t blob_size = value.size(); + + switch (simple_type->get_type()) { + case ST_char: + case ST_uint8: + case ST_int8: + _array_size_range.validate(blob_size, range_error); + if (_num_length_bytes != 0) { + nassertv(_num_length_bytes == 2); + do_pack_uint16(pack_data.get_write_pointer(2), blob_size); + } + pack_data.append_data((const char *)value.data(), blob_size); + break; + + default: + pack_error = true; + } +} + /** * Packs the arrayParameter's specified default value (or a sensible default * if no value is specified) into the stream. Returns true if the default @@ -339,6 +371,46 @@ unpack_string(const char *data, size_t length, size_t &p, string &value, } } +/** + * Unpacks the current numeric or string value from the stream. + */ +void DCArrayParameter:: +unpack_blob(const char *data, size_t length, size_t &p, vector_uchar &value, + bool &pack_error, bool &range_error) const { + // We can only unpack a string if the array element type is char or int8. + DCSimpleParameter *simple_type = _element_type->as_simple_parameter(); + if (simple_type == nullptr) { + pack_error = true; + return; + } + + size_t blob_size; + + switch (simple_type->get_type()) { + case ST_char: + case ST_uint8: + case ST_int8: + if (_num_length_bytes != 0) { + blob_size = do_unpack_uint16(data + p); + p += 2; + } else { + nassertv(_array_size >= 0); + blob_size = _array_size; + } + if (p + blob_size > length) { + pack_error = true; + return; + } + value = vector_uchar((const unsigned char *)data + p, + (const unsigned char *)data + p + blob_size); + p += blob_size; + break; + + default: + pack_error = true; + } +} + /** * Returns true if the other interface is bitwise the same as this one--that * is, a uint32 only matches a uint32, etc. Names of components, and range diff --git a/direct/src/dcparser/dcArrayParameter.h b/direct/src/dcparser/dcArrayParameter.h index 97fb32bdf0..d81707a9e6 100644 --- a/direct/src/dcparser/dcArrayParameter.h +++ b/direct/src/dcparser/dcArrayParameter.h @@ -51,9 +51,13 @@ public: virtual void generate_hash(HashGenerator &hashgen) const; virtual void pack_string(DCPackData &pack_data, const std::string &value, bool &pack_error, bool &range_error) const; + virtual void pack_blob(DCPackData &pack_data, const vector_uchar &value, + bool &pack_error, bool &range_error) const; virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const; virtual void unpack_string(const char *data, size_t length, size_t &p, std::string &value, bool &pack_error, bool &range_error) const; + virtual void unpack_blob(const char *data, size_t length, size_t &p, + vector_uchar &value, bool &pack_error, bool &range_error) const; protected: virtual bool do_check_match(const DCPackerInterface *other) const; diff --git a/direct/src/dcparser/dcAtomicField.cxx b/direct/src/dcparser/dcAtomicField.cxx index 3efebfa2d6..eb5607e55c 100644 --- a/direct/src/dcparser/dcAtomicField.cxx +++ b/direct/src/dcparser/dcAtomicField.cxx @@ -88,11 +88,11 @@ get_element(int n) const { * If the element is an array-type element, the returned value will include * the two-byte length preceding the array data. * - * This is deprecated; use get_element() instead. + * @deprecated use get_element() instead. */ -string DCAtomicField:: +vector_uchar DCAtomicField:: get_element_default(int n) const { - nassertr(n >= 0 && n < (int)_elements.size(), string()); + nassertr(n >= 0 && n < (int)_elements.size(), vector_uchar()); return _elements[n]->get_default_value(); } @@ -100,7 +100,7 @@ get_element_default(int n) const { * Returns true if the nth element of the field has a default value specified, * false otherwise. * - * This is deprecated; use get_element() instead. + * @deprecated use get_element() instead. */ bool DCAtomicField:: has_element_default(int n) const { @@ -113,7 +113,7 @@ has_element_default(int n) const { * for documentary purposes; it does not generally affect operation. If a * name is not specified, this will be the empty string. * - * This method is deprecated; use get_element()->get_name() instead. + * @deprecated use get_element()->get_name() instead. */ string DCAtomicField:: get_element_name(int n) const { diff --git a/direct/src/dcparser/dcAtomicField.h b/direct/src/dcparser/dcAtomicField.h index 9eb92e2527..418b1b768f 100644 --- a/direct/src/dcparser/dcAtomicField.h +++ b/direct/src/dcparser/dcAtomicField.h @@ -40,7 +40,7 @@ PUBLISHED: DCParameter *get_element(int n) const; // These five methods are deprecated and will be removed soon. - std::string get_element_default(int n) const; + vector_uchar get_element_default(int n) const; bool has_element_default(int n) const; std::string get_element_name(int n) const; DCSubatomicType get_element_type(int n) const; diff --git a/direct/src/dcparser/dcClass.cxx b/direct/src/dcparser/dcClass.cxx index 374ed47d3c..9487c5e323 100644 --- a/direct/src/dcparser/dcClass.cxx +++ b/direct/src/dcparser/dcClass.cxx @@ -587,7 +587,7 @@ receive_update_other(PyObject *distobj, DatagramIterator &di) const { */ void DCClass:: direct_update(PyObject *distobj, const string &field_name, - const string &value_blob) { + const vector_uchar &value_blob) { DCField *field = get_field_by_name(field_name); nassertv_always(field != nullptr); @@ -606,7 +606,14 @@ direct_update(PyObject *distobj, const string &field_name, void DCClass:: direct_update(PyObject *distobj, const string &field_name, const Datagram &datagram) { - direct_update(distobj, field_name, datagram.get_message()); + DCField *field = get_field_by_name(field_name); + nassertv_always(field != nullptr); + + DCPacker packer; + packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false); + packer.begin_unpack(field); + field->receive_update(packer, distobj); + packer.end_unpack(); } #endif // HAVE_PYTHON diff --git a/direct/src/dcparser/dcClass.h b/direct/src/dcparser/dcClass.h index cd69716470..94feae1c19 100644 --- a/direct/src/dcparser/dcClass.h +++ b/direct/src/dcparser/dcClass.h @@ -95,7 +95,7 @@ PUBLISHED: void receive_update_other(PyObject *distobj, DatagramIterator &di) const; void direct_update(PyObject *distobj, const std::string &field_name, - const std::string &value_blob); + const vector_uchar &value_blob); void direct_update(PyObject *distobj, const std::string &field_name, const Datagram &datagram); bool pack_required_field(Datagram &datagram, PyObject *distobj, diff --git a/direct/src/dcparser/dcField.I b/direct/src/dcparser/dcField.I index aabf5cf114..e6aa4a4672 100644 --- a/direct/src/dcparser/dcField.I +++ b/direct/src/dcparser/dcField.I @@ -42,7 +42,7 @@ has_default_value() const { * explicitly set (e.g. has_default_value() returns true), returns that * value; otherwise, returns an implicit default for the field. */ -INLINE const std::string &DCField:: +INLINE const vector_uchar &DCField:: get_default_value() const { if (_default_value_stale) { ((DCField *)this)->refresh_default_value(); @@ -172,8 +172,8 @@ set_class(DCClass *dclass) { * Establishes a default value for this field. */ INLINE void DCField:: -set_default_value(const std::string &default_value) { - _default_value = default_value; +set_default_value(vector_uchar default_value) { + _default_value = std::move(default_value); _has_default_value = true; _default_value_stale = false; } diff --git a/direct/src/dcparser/dcField.cxx b/direct/src/dcparser/dcField.cxx index 4f255dea7d..5880fd5097 100644 --- a/direct/src/dcparser/dcField.cxx +++ b/direct/src/dcparser/dcField.cxx @@ -162,7 +162,7 @@ as_parameter() const { * is an error. */ string DCField:: -format_data(const string &packed_data, bool show_field_names) { +format_data(const vector_uchar &packed_data, bool show_field_names) { DCPacker packer; packer.set_unpack_data(packed_data); packer.begin_unpack(this); @@ -178,20 +178,20 @@ format_data(const string &packed_data, bool show_field_names) { * above) that represents the value of this field, parse the string and return * the corresponding packed data. Returns empty string if there is an error. */ -string DCField:: +vector_uchar DCField:: parse_string(const string &formatted_string) { DCPacker packer; packer.begin_pack(this); if (!packer.parse_and_pack(formatted_string)) { // Parse error. - return string(); + return vector_uchar(); } if (!packer.end_pack()) { // Data type mismatch. - return string(); + return vector_uchar(); } - return packer.get_string(); + return packer.get_bytes(); } /** @@ -200,7 +200,7 @@ parse_string(const string &formatted_string) { * record. Returns true if all fields are valid, false otherwise. */ bool DCField:: -validate_ranges(const string &packed_data) const { +validate_ranges(const vector_uchar &packed_data) const { DCPacker packer; packer.set_unpack_data(packed_data); packer.begin_unpack(this); @@ -209,7 +209,7 @@ validate_ranges(const string &packed_data) const { return false; } - return (packer.get_num_unpacked_bytes() == packed_data.length()); + return (packer.get_num_unpacked_bytes() == packed_data.size()); } #ifdef HAVE_PYTHON @@ -488,7 +488,7 @@ pack_default_value(DCPackData &pack_data, bool &) const { // The default behavior is to pack the default value if we got it; // otherwise, to return false and let the packer visit our nested elements. if (!_default_value_stale) { - pack_data.append_data(_default_value.data(), _default_value.length()); + pack_data.append_data((const char *)_default_value.data(), _default_value.size()); return true; } @@ -566,7 +566,8 @@ refresh_default_value() { if (!packer.end_pack()) { std::cerr << "Error while packing default value for " << get_name() << "\n"; } else { - _default_value.assign(packer.get_data(), packer.get_length()); + const unsigned char *data = (const unsigned char *)packer.get_data(); + _default_value = vector_uchar(data, data + packer.get_length()); } _default_value_stale = false; } diff --git a/direct/src/dcparser/dcField.h b/direct/src/dcparser/dcField.h index dc06ff76f2..96b7da4487 100644 --- a/direct/src/dcparser/dcField.h +++ b/direct/src/dcparser/dcField.h @@ -53,13 +53,13 @@ PUBLISHED: virtual DCParameter *as_parameter(); virtual const DCParameter *as_parameter() const; - std::string format_data(const std::string &packed_data, bool show_field_names = true); - std::string parse_string(const std::string &formatted_string); + std::string format_data(const vector_uchar &packed_data, bool show_field_names = true); + vector_uchar parse_string(const std::string &formatted_string); - bool validate_ranges(const std::string &packed_data) const; + bool validate_ranges(const vector_uchar &packed_data) const; INLINE bool has_default_value() const; - INLINE const std::string &get_default_value() const; + INLINE const vector_uchar &get_default_value() const; INLINE bool is_bogus_field() const; @@ -98,7 +98,7 @@ public: INLINE void set_number(int number); INLINE void set_class(DCClass *dclass); - INLINE void set_default_value(const std::string &default_value); + INLINE void set_default_value(vector_uchar default_value); #ifdef HAVE_PYTHON static std::string get_pystr(PyObject *value); @@ -115,7 +115,7 @@ protected: bool _bogus_field; private: - std::string _default_value; + vector_uchar _default_value; #ifdef WITHIN_PANDA PStatCollector _field_update_pcollector; diff --git a/direct/src/dcparser/dcLexer.cxx.prebuilt b/direct/src/dcparser/dcLexer.cxx.prebuilt index 660f96b5cb..fece97f2ae 100644 --- a/direct/src/dcparser/dcLexer.cxx.prebuilt +++ b/direct/src/dcparser/dcLexer.cxx.prebuilt @@ -1,6 +1,6 @@ -#line 2 "lex.yy.c" +#line 2 "built/tmp/dcLexer.lxx.cxx" -#line 4 "lex.yy.c" +#line 4 "built/tmp/dcLexer.lxx.cxx" #define YY_INT_ALIGNED short int @@ -28,13 +28,23 @@ #define FLEX_SCANNER #define YY_FLEX_MAJOR_VERSION 2 #define YY_FLEX_MINOR_VERSION 5 -#define YY_FLEX_SUBMINOR_VERSION 35 +#define YY_FLEX_SUBMINOR_VERSION 37 #if YY_FLEX_SUBMINOR_VERSION > 0 #define FLEX_BETA #endif /* First, we deal with platform-specific or compiler-specific issues. */ +#if defined(__FreeBSD__) +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif +#include +#include +#else +#define __dead2 +#endif + /* begin standard C headers. */ #include #include @@ -50,10 +60,11 @@ /* C99 systems have . Non-C99 systems may or may not. */ -#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#if defined(__FreeBSD__) || \ + (defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, - * if you want the limit (max/min) macros for int types. + * if you want the limit (max/min) macros for int types. */ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS 1 @@ -70,10 +81,9 @@ typedef uint32_t flex_uint32_t; typedef signed char flex_int8_t; typedef short int flex_int16_t; typedef int flex_int32_t; -typedef unsigned char flex_uint8_t; +typedef unsigned char flex_uint8_t; typedef unsigned short int flex_uint16_t; typedef unsigned int flex_uint32_t; -#endif /* ! C99 */ /* Limits of integral types. */ #ifndef INT8_MIN @@ -104,6 +114,8 @@ typedef unsigned int flex_uint32_t; #define UINT32_MAX (4294967295U) #endif +#endif /* ! C99 */ + #endif /* ! FLEXINT_H */ #ifdef __cplusplus @@ -172,7 +184,12 @@ typedef unsigned int flex_uint32_t; typedef struct yy_buffer_state *YY_BUFFER_STATE; #endif -extern int dcyyleng; +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +extern yy_size_t dcyyleng; extern FILE *dcyyin, *dcyyout; @@ -181,7 +198,7 @@ extern FILE *dcyyin, *dcyyout; #define EOB_ACT_LAST_MATCH 2 #define YY_LESS_LINENO(n) - + /* Return all but the first "n" matched characters back to the input stream. */ #define yyless(n) \ do \ @@ -198,11 +215,6 @@ extern FILE *dcyyin, *dcyyout; #define unput(c) yyunput( c, (yytext_ptr) ) -#ifndef YY_TYPEDEF_YY_SIZE_T -#define YY_TYPEDEF_YY_SIZE_T -typedef size_t yy_size_t; -#endif - #ifndef YY_STRUCT_YY_BUFFER_STATE #define YY_STRUCT_YY_BUFFER_STATE struct yy_buffer_state @@ -220,7 +232,7 @@ struct yy_buffer_state /* Number of characters read into yy_ch_buf, not including EOB * characters. */ - int yy_n_chars; + yy_size_t yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to @@ -243,7 +255,7 @@ struct yy_buffer_state int yy_bs_lineno; /**< The line count. */ int yy_bs_column; /**< The column count. */ - + /* Whether to try to fill the input buffer when we reach the * end of it. */ @@ -282,6 +294,7 @@ static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ #define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ : NULL) +#define yy_current_buffer YY_CURRENT_BUFFER /* Same as previous macro, but useful when we know that the buffer stack is not * NULL or when we need an lvalue. For internal use only. @@ -290,8 +303,8 @@ static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ /* yy_hold_char holds the character lost when dcyytext is formed. */ static char yy_hold_char; -static int yy_n_chars; /* number of characters read into yy_ch_buf */ -int dcyyleng; +static yy_size_t yy_n_chars; /* number of characters read into yy_ch_buf */ +yy_size_t dcyyleng; /* Points to current character in buffer. */ static char *yy_c_buf_p = (char *) 0; @@ -319,7 +332,7 @@ static void dcyy_init_buffer (YY_BUFFER_STATE b,FILE *file ); YY_BUFFER_STATE dcyy_scan_buffer (char *base,yy_size_t size ); YY_BUFFER_STATE dcyy_scan_string (yyconst char *yy_str ); -YY_BUFFER_STATE dcyy_scan_bytes (yyconst char *bytes,int len ); +YY_BUFFER_STATE dcyy_scan_bytes (yyconst char *bytes,yy_size_t len ); void *dcyyalloc (yy_size_t ); void *dcyyrealloc (void *,yy_size_t ); @@ -365,7 +378,7 @@ extern char *dcyytext; static yy_state_type yy_get_previous_state (void ); static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); static int yy_get_next_buffer (void ); -static void yy_fatal_error (yyconst char msg[] ); +static void yy_fatal_error (yyconst char msg[] ) __dead2; /* Done after the current pattern has been matched and before the * corresponding action - sets up dcyytext. @@ -573,14 +586,14 @@ int dcyy_flex_debug = 0; #define YY_MORE_ADJ 0 #define YY_RESTORE_YY_MORE_OFFSET char *dcyytext; -#line 1 "dcLexer.lxx" +#line 1 "direct/src/dcparser/dcLexer.lxx" /* // Filename: dcLexer.lxx // Created by: drose (05Oct00) -// +// //////////////////////////////////////////////////////////////////// */ -#line 9 "dcLexer.lxx" +#line 9 "direct/src/dcparser/dcLexer.lxx" #include "dcLexerDefs.h" #include "dcParserDefs.h" #include "dcParser.h" @@ -678,12 +691,12 @@ dcyyerror(const std::string &msg) { if (!dc_filename.empty()) { cerr << " in " << dc_filename; } - cerr + cerr << " at line " << line_number << ", column " << col_number << ":\n" << current_line << "\n"; - indent(cerr, col_number-1) + indent(cerr, col_number-1) << "^\n" << msg << "\n\n"; - + error_count++; } @@ -695,10 +708,10 @@ dcyywarning(const std::string &msg) { if (!dc_filename.empty()) { cerr << " in " << dc_filename; } - cerr + cerr << " at line " << line_number << ", column " << col_number << ":\n" << current_line << "\n"; - indent(cerr, col_number-1) + indent(cerr, col_number-1) << "^\n" << msg << "\n\n"; warning_count++; @@ -708,7 +721,7 @@ dcyywarning(const std::string &msg) { // stdio FILE pointer. This is flex-specific. static void input_chars(char *buffer, int &result, int max_size) { - nassertv(input_p != NULL); + nassertv(input_p != nullptr); if (*input_p) { input_p->read(buffer, max_size); result = input_p->gcount(); @@ -729,7 +742,7 @@ input_chars(char *buffer, int &result, int max_size) { // Truncate it at the newline. char *end = strchr(current_line, '\n'); - if (end != NULL) { + if (end != nullptr) { *end = '\0'; } } @@ -888,9 +901,9 @@ scan_quoted_string(char quote_mark) { // scan_hex_string reads a string of hexadecimal digits delimited by // angle brackets and returns the representative string. -static std::string +static vector_uchar scan_hex_string() { - std::string result; + vector_uchar result; // We don't touch the current line number and column number during // scanning, so that if we detect an error while scanning the string @@ -920,24 +933,24 @@ scan_hex_string() { line_number = line; col_number = col; dcyyerror("Invalid hex digit."); - return std::string(); + return vector_uchar(); } odd = !odd; if (odd) { last = value; } else { - result += (char)((last << 4) | value); + result.push_back((unsigned char)((last << 4) | value)); } c = read_char(line, col); } if (c == EOF) { dcyyerror("This hex string is unterminated."); - return std::string(); + return vector_uchar(); } else if (odd) { dcyyerror("Odd number of hex digits."); - return std::string(); + return vector_uchar(); } line_number = line; @@ -957,7 +970,7 @@ eat_c_comment() { int col = col_number; int c, last_c; - + last_c = '\0'; c = read_char(line, col); while (c != EOF && !(last_c == '*' && c == '/')) { @@ -984,7 +997,7 @@ inline void accept() { col_number += dcyyleng; } -#line 984 "lex.yy.c" +#line 1001 "built/tmp/dcLexer.lxx.cxx" #define INITIAL 0 @@ -993,6 +1006,7 @@ inline void accept() { * down here because we want the user's section 1 to have been scanned first. * The user has a chance to override it with an option. */ +#include #endif #ifndef YY_EXTRA_TYPE @@ -1022,7 +1036,7 @@ FILE *dcyyget_out (void ); void dcyyset_out (FILE * out_str ); -int dcyyget_leng (void ); +yy_size_t dcyyget_leng (void ); char *dcyyget_text (void ); @@ -1042,8 +1056,10 @@ extern int dcyywrap (void ); #endif #endif +#ifndef YY_NO_UNPUT static void yyunput (int c,char *buf_ptr ); - +#endif + #ifndef yytext_ptr static void yy_flex_strncpy (char *,yyconst char *,int ); #endif @@ -1072,7 +1088,7 @@ static int input (void ); /* This used to be an fputs(), but since the string might contain NUL's, * we now use fwrite(). */ -#define ECHO fwrite( dcyytext, dcyyleng, 1, dcyyout ) +#define ECHO do { if (fwrite( dcyytext, dcyyleng, 1, dcyyout )) {} } while (0) #endif /* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, @@ -1083,7 +1099,7 @@ static int input (void ); if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ { \ int c = '*'; \ - int n; \ + size_t n; \ for ( n = 0; n < max_size && \ (c = getc( dcyyin )) != EOF && c != '\n'; ++n ) \ buf[n] = (char) c; \ @@ -1161,11 +1177,11 @@ extern int dcyylex (void); */ YY_DECL { - register yy_state_type yy_current_state; - register char *yy_cp, *yy_bp; - register int yy_act; - -#line 415 "dcLexer.lxx" + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + +#line 419 "direct/src/dcparser/dcLexer.lxx" @@ -1176,7 +1192,7 @@ YY_DECL } -#line 1177 "lex.yy.c" +#line 1196 "built/tmp/dcLexer.lxx.cxx" if ( !(yy_init) ) { @@ -1220,7 +1236,7 @@ YY_DECL yy_match: do { - register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; if ( yy_accept[yy_current_state] ) { (yy_last_accepting_state) = yy_current_state; @@ -1262,7 +1278,7 @@ do_action: /* This label is used only to access EOF actions. */ case 1: /* rule 1 can match eol */ YY_RULE_SETUP -#line 425 "dcLexer.lxx" +#line 429 "direct/src/dcparser/dcLexer.lxx" { // New line. Save a copy of the line so we can print it out for the // benefit of the user in case we get an error. @@ -1279,32 +1295,32 @@ YY_RULE_SETUP YY_BREAK case 2: YY_RULE_SETUP -#line 439 "dcLexer.lxx" -{ +#line 443 "direct/src/dcparser/dcLexer.lxx" +{ // Eat whitespace. accept(); } YY_BREAK case 3: YY_RULE_SETUP -#line 444 "dcLexer.lxx" -{ +#line 448 "direct/src/dcparser/dcLexer.lxx" +{ // Eat C++-style comments. accept(); } YY_BREAK case 4: YY_RULE_SETUP -#line 449 "dcLexer.lxx" +#line 453 "direct/src/dcparser/dcLexer.lxx" { // Eat C-style comments. accept(); - eat_c_comment(); + eat_c_comment(); } YY_BREAK case 5: YY_RULE_SETUP -#line 456 "dcLexer.lxx" +#line 460 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_DCLASS; @@ -1312,7 +1328,7 @@ YY_RULE_SETUP YY_BREAK case 6: YY_RULE_SETUP -#line 461 "dcLexer.lxx" +#line 465 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_STRUCT; @@ -1320,7 +1336,7 @@ YY_RULE_SETUP YY_BREAK case 7: YY_RULE_SETUP -#line 466 "dcLexer.lxx" +#line 470 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_FROM; @@ -1328,7 +1344,7 @@ YY_RULE_SETUP YY_BREAK case 8: YY_RULE_SETUP -#line 471 "dcLexer.lxx" +#line 475 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_IMPORT; @@ -1336,7 +1352,7 @@ YY_RULE_SETUP YY_BREAK case 9: YY_RULE_SETUP -#line 476 "dcLexer.lxx" +#line 480 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_KEYWORD; @@ -1344,7 +1360,7 @@ YY_RULE_SETUP YY_BREAK case 10: YY_RULE_SETUP -#line 481 "dcLexer.lxx" +#line 485 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_TYPEDEF; @@ -1352,7 +1368,7 @@ YY_RULE_SETUP YY_BREAK case 11: YY_RULE_SETUP -#line 486 "dcLexer.lxx" +#line 490 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_SWITCH; @@ -1360,7 +1376,7 @@ YY_RULE_SETUP YY_BREAK case 12: YY_RULE_SETUP -#line 491 "dcLexer.lxx" +#line 495 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_CASE; @@ -1368,7 +1384,7 @@ YY_RULE_SETUP YY_BREAK case 13: YY_RULE_SETUP -#line 496 "dcLexer.lxx" +#line 500 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_DEFAULT; @@ -1376,7 +1392,7 @@ YY_RULE_SETUP YY_BREAK case 14: YY_RULE_SETUP -#line 501 "dcLexer.lxx" +#line 505 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_BREAK; @@ -1384,7 +1400,7 @@ YY_RULE_SETUP YY_BREAK case 15: YY_RULE_SETUP -#line 506 "dcLexer.lxx" +#line 510 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_INT8; @@ -1392,7 +1408,7 @@ YY_RULE_SETUP YY_BREAK case 16: YY_RULE_SETUP -#line 511 "dcLexer.lxx" +#line 515 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_INT16; @@ -1400,7 +1416,7 @@ YY_RULE_SETUP YY_BREAK case 17: YY_RULE_SETUP -#line 516 "dcLexer.lxx" +#line 520 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_INT32; @@ -1408,7 +1424,7 @@ YY_RULE_SETUP YY_BREAK case 18: YY_RULE_SETUP -#line 521 "dcLexer.lxx" +#line 525 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_INT64; @@ -1416,7 +1432,7 @@ YY_RULE_SETUP YY_BREAK case 19: YY_RULE_SETUP -#line 526 "dcLexer.lxx" +#line 530 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_UINT8; @@ -1424,7 +1440,7 @@ YY_RULE_SETUP YY_BREAK case 20: YY_RULE_SETUP -#line 531 "dcLexer.lxx" +#line 535 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_UINT16; @@ -1432,7 +1448,7 @@ YY_RULE_SETUP YY_BREAK case 21: YY_RULE_SETUP -#line 536 "dcLexer.lxx" +#line 540 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_UINT32; @@ -1440,7 +1456,7 @@ YY_RULE_SETUP YY_BREAK case 22: YY_RULE_SETUP -#line 541 "dcLexer.lxx" +#line 545 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_UINT64; @@ -1448,7 +1464,7 @@ YY_RULE_SETUP YY_BREAK case 23: YY_RULE_SETUP -#line 546 "dcLexer.lxx" +#line 550 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_FLOAT64; @@ -1456,7 +1472,7 @@ YY_RULE_SETUP YY_BREAK case 24: YY_RULE_SETUP -#line 551 "dcLexer.lxx" +#line 555 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_STRING; @@ -1464,7 +1480,7 @@ YY_RULE_SETUP YY_BREAK case 25: YY_RULE_SETUP -#line 556 "dcLexer.lxx" +#line 560 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_BLOB; @@ -1472,7 +1488,7 @@ YY_RULE_SETUP YY_BREAK case 26: YY_RULE_SETUP -#line 561 "dcLexer.lxx" +#line 565 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_BLOB32; @@ -1480,7 +1496,7 @@ YY_RULE_SETUP YY_BREAK case 27: YY_RULE_SETUP -#line 566 "dcLexer.lxx" +#line 570 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_INT8ARRAY; @@ -1488,7 +1504,7 @@ YY_RULE_SETUP YY_BREAK case 28: YY_RULE_SETUP -#line 571 "dcLexer.lxx" +#line 575 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_INT16ARRAY; @@ -1496,7 +1512,7 @@ YY_RULE_SETUP YY_BREAK case 29: YY_RULE_SETUP -#line 576 "dcLexer.lxx" +#line 580 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_INT32ARRAY; @@ -1504,7 +1520,7 @@ YY_RULE_SETUP YY_BREAK case 30: YY_RULE_SETUP -#line 581 "dcLexer.lxx" +#line 585 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_UINT8ARRAY; @@ -1512,7 +1528,7 @@ YY_RULE_SETUP YY_BREAK case 31: YY_RULE_SETUP -#line 586 "dcLexer.lxx" +#line 590 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_UINT16ARRAY; @@ -1520,7 +1536,7 @@ YY_RULE_SETUP YY_BREAK case 32: YY_RULE_SETUP -#line 591 "dcLexer.lxx" +#line 595 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_UINT32ARRAY; @@ -1528,7 +1544,7 @@ YY_RULE_SETUP YY_BREAK case 33: YY_RULE_SETUP -#line 596 "dcLexer.lxx" +#line 600 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_UINT32UINT8ARRAY; @@ -1536,7 +1552,7 @@ YY_RULE_SETUP YY_BREAK case 34: YY_RULE_SETUP -#line 601 "dcLexer.lxx" +#line 605 "direct/src/dcparser/dcLexer.lxx" { accept(); return KW_CHAR; @@ -1544,8 +1560,8 @@ YY_RULE_SETUP YY_BREAK case 35: YY_RULE_SETUP -#line 606 "dcLexer.lxx" -{ +#line 610 "direct/src/dcparser/dcLexer.lxx" +{ // An unsigned integer number. accept(); @@ -1564,14 +1580,14 @@ YY_RULE_SETUP dcyylval.u.uint64 = next_value + (*p - '0'); ++p; } - + return UNSIGNED_INTEGER; } YY_BREAK case 36: YY_RULE_SETUP -#line 629 "dcLexer.lxx" -{ +#line 633 "direct/src/dcparser/dcLexer.lxx" +{ // A signed integer number. accept(); @@ -1612,17 +1628,17 @@ YY_RULE_SETUP dcyyerror("Number out of range."); dcyylval.u.int64 = 1; } - } - + } + return SIGNED_INTEGER; } YY_BREAK case 37: YY_RULE_SETUP -#line 675 "dcLexer.lxx" +#line 679 "direct/src/dcparser/dcLexer.lxx" { // A hexadecimal integer number. - accept(); + accept(); // As above, we'll decode the hex string by hand. dcyylval.str = dcyytext; @@ -1644,23 +1660,23 @@ YY_RULE_SETUP ++p; } - return UNSIGNED_INTEGER; + return UNSIGNED_INTEGER; } YY_BREAK case 38: YY_RULE_SETUP -#line 702 "dcLexer.lxx" -{ +#line 706 "direct/src/dcparser/dcLexer.lxx" +{ // A floating-point number. - accept(); - dcyylval.u.real = patof(dcyytext); + accept(); + dcyylval.u.real = patof(dcyytext); dcyylval.str = dcyytext; - return REAL; + return REAL; } YY_BREAK case 39: YY_RULE_SETUP -#line 710 "dcLexer.lxx" +#line 714 "direct/src/dcparser/dcLexer.lxx" { // Quoted string. accept(); @@ -1670,7 +1686,7 @@ YY_RULE_SETUP YY_BREAK case 40: YY_RULE_SETUP -#line 717 "dcLexer.lxx" +#line 721 "direct/src/dcparser/dcLexer.lxx" { // Single-quoted string. accept(); @@ -1680,25 +1696,25 @@ YY_RULE_SETUP YY_BREAK case 41: YY_RULE_SETUP -#line 724 "dcLexer.lxx" +#line 728 "direct/src/dcparser/dcLexer.lxx" { // Long hex string. accept(); - dcyylval.str = scan_hex_string(); + dcyylval.bytes = scan_hex_string(); return HEX_STRING; } YY_BREAK case 42: YY_RULE_SETUP -#line 731 "dcLexer.lxx" -{ +#line 735 "direct/src/dcparser/dcLexer.lxx" +{ // Identifier or keyword. accept(); dcyylval.str = dcyytext; - if (dc_file != (DCFile *)NULL) { + if (dc_file != nullptr) { const DCKeyword *keyword = dc_file->get_keyword_by_name(dcyylval.str); - if (keyword != (DCKeyword *)NULL) { + if (keyword != nullptr) { dcyylval.u.keyword = keyword; return KEYWORD; } @@ -1708,19 +1724,19 @@ YY_RULE_SETUP YY_BREAK case 43: YY_RULE_SETUP -#line 747 "dcLexer.lxx" +#line 751 "direct/src/dcparser/dcLexer.lxx" { // Send any other printable character as itself. - accept(); + accept(); return dcyytext[0]; } YY_BREAK case 44: YY_RULE_SETUP -#line 753 "dcLexer.lxx" +#line 757 "direct/src/dcparser/dcLexer.lxx" ECHO; YY_BREAK -#line 1721 "lex.yy.c" +#line 1740 "built/tmp/dcLexer.lxx.cxx" case YY_STATE_EOF(INITIAL): yyterminate(); @@ -1862,9 +1878,9 @@ case YY_STATE_EOF(INITIAL): */ static int yy_get_next_buffer (void) { - register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; - register char *source = (yytext_ptr); - register int number_to_move, i; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = (yytext_ptr); + int number_to_move, i; int ret_val; if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) @@ -1906,21 +1922,21 @@ static int yy_get_next_buffer (void) else { - int num_to_read = + yy_size_t num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; while ( num_to_read <= 0 ) { /* Not enough room in the buffer - grow it. */ /* just a shorter name for the current buffer */ - YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; int yy_c_buf_p_offset = (int) ((yy_c_buf_p) - b->yy_ch_buf); if ( b->yy_is_our_buffer ) { - int new_size = b->yy_buf_size * 2; + yy_size_t new_size = b->yy_buf_size * 2; if ( new_size <= 0 ) b->yy_buf_size += b->yy_buf_size / 8; @@ -1951,7 +1967,7 @@ static int yy_get_next_buffer (void) /* Read in more data. */ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), - (yy_n_chars), (size_t) num_to_read ); + (yy_n_chars), num_to_read ); YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); } @@ -1996,14 +2012,14 @@ static int yy_get_next_buffer (void) static yy_state_type yy_get_previous_state (void) { - register yy_state_type yy_current_state; - register char *yy_cp; - + yy_state_type yy_current_state; + char *yy_cp; + yy_current_state = (yy_start); for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) { - register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); if ( yy_accept[yy_current_state] ) { (yy_last_accepting_state) = yy_current_state; @@ -2028,10 +2044,10 @@ static int yy_get_next_buffer (void) */ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) { - register int yy_is_jam; - register char *yy_cp = (yy_c_buf_p); + int yy_is_jam; + char *yy_cp = (yy_c_buf_p); - register YY_CHAR yy_c = 1; + YY_CHAR yy_c = 1; if ( yy_accept[yy_current_state] ) { (yy_last_accepting_state) = yy_current_state; @@ -2046,13 +2062,14 @@ static int yy_get_next_buffer (void) yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; yy_is_jam = (yy_current_state == 164); - return yy_is_jam ? 0 : yy_current_state; + return yy_is_jam ? 0 : yy_current_state; } - static void yyunput (int c, register char * yy_bp ) +#ifndef YY_NO_UNPUT + static void yyunput (int c, char * yy_bp ) { - register char *yy_cp; - + char *yy_cp; + yy_cp = (yy_c_buf_p); /* undo effects of setting up dcyytext */ @@ -2061,10 +2078,10 @@ static int yy_get_next_buffer (void) if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) { /* need to shift things up to make room */ /* +2 for EOB chars. */ - register int number_to_move = (yy_n_chars) + 2; - register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + yy_size_t number_to_move = (yy_n_chars) + 2; + char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; - register char *source = + char *source = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) @@ -2085,6 +2102,7 @@ static int yy_get_next_buffer (void) (yy_hold_char) = *yy_cp; (yy_c_buf_p) = yy_cp; } +#endif /* ifndef YY_NO_UNPUT */ #ifndef YY_NO_INPUT #ifdef __cplusplus @@ -2095,7 +2113,7 @@ static int yy_get_next_buffer (void) { int c; - + *(yy_c_buf_p) = (yy_hold_char); if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) @@ -2110,7 +2128,7 @@ static int yy_get_next_buffer (void) else { /* need more input */ - int offset = (yy_c_buf_p) - (yytext_ptr); + yy_size_t offset = (yy_c_buf_p) - (yytext_ptr); ++(yy_c_buf_p); switch ( yy_get_next_buffer( ) ) @@ -2162,12 +2180,12 @@ static int yy_get_next_buffer (void) /** Immediately switch to a different input stream. * @param input_file A readable stream. - * + * * @note This function does not reset the start condition to @c INITIAL . */ void dcyyrestart (FILE * input_file ) { - + if ( ! YY_CURRENT_BUFFER ){ dcyyensure_buffer_stack (); YY_CURRENT_BUFFER_LVALUE = @@ -2180,11 +2198,11 @@ static int yy_get_next_buffer (void) /** Switch to a different input buffer. * @param new_buffer The new input buffer. - * + * */ void dcyy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) { - + /* TODO. We should be able to replace this entire function body * with * dcyypop_buffer_state(); @@ -2224,13 +2242,13 @@ static void dcyy_load_buffer_state (void) /** Allocate and initialize an input buffer state. * @param file A readable stream. * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. - * + * * @return the allocated buffer state. */ YY_BUFFER_STATE dcyy_create_buffer (FILE * file, int size ) { YY_BUFFER_STATE b; - + b = (YY_BUFFER_STATE) dcyyalloc(sizeof( struct yy_buffer_state ) ); if ( ! b ) YY_FATAL_ERROR( "out of dynamic memory in dcyy_create_buffer()" ); @@ -2253,11 +2271,11 @@ static void dcyy_load_buffer_state (void) /** Destroy the buffer. * @param b a buffer created with dcyy_create_buffer() - * + * */ void dcyy_delete_buffer (YY_BUFFER_STATE b ) { - + if ( ! b ) return; @@ -2270,10 +2288,6 @@ static void dcyy_load_buffer_state (void) dcyyfree((void *) b ); } -#ifndef __cplusplus -extern int isatty (int ); -#endif /* __cplusplus */ - /* Initializes or reinitializes a buffer. * This function is sometimes called more than once on the same buffer, * such as during a dcyyrestart() or at EOF. @@ -2282,7 +2296,7 @@ extern int isatty (int ); { int oerrno = errno; - + dcyy_flush_buffer(b ); b->yy_input_file = file; @@ -2298,13 +2312,13 @@ extern int isatty (int ); } b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; - + errno = oerrno; } /** Discard all buffered characters. On the next scan, YY_INPUT will be called. * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. - * + * */ void dcyy_flush_buffer (YY_BUFFER_STATE b ) { @@ -2333,7 +2347,7 @@ extern int isatty (int ); * the current state. This function will allocate the stack * if necessary. * @param new_buffer The new state. - * + * */ void dcyypush_buffer_state (YY_BUFFER_STATE new_buffer ) { @@ -2363,7 +2377,7 @@ void dcyypush_buffer_state (YY_BUFFER_STATE new_buffer ) /** Removes and deletes the top of the stack, if present. * The next element becomes the new top. - * + * */ void dcyypop_buffer_state (void) { @@ -2386,8 +2400,8 @@ void dcyypop_buffer_state (void) */ static void dcyyensure_buffer_stack (void) { - int num_to_alloc; - + yy_size_t num_to_alloc; + if (!(yy_buffer_stack)) { /* First allocation is just for 2 elements, since we don't know if this @@ -2400,9 +2414,9 @@ static void dcyyensure_buffer_stack (void) ); if ( ! (yy_buffer_stack) ) YY_FATAL_ERROR( "out of dynamic memory in dcyyensure_buffer_stack()" ); - + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); - + (yy_buffer_stack_max) = num_to_alloc; (yy_buffer_stack_top) = 0; return; @@ -2430,13 +2444,13 @@ static void dcyyensure_buffer_stack (void) /** Setup the input buffer state to scan directly from a user-specified character buffer. * @param base the character buffer * @param size the size in bytes of the character buffer - * - * @return the newly allocated buffer state object. + * + * @return the newly allocated buffer state object. */ YY_BUFFER_STATE dcyy_scan_buffer (char * base, yy_size_t size ) { YY_BUFFER_STATE b; - + if ( size < 2 || base[size-2] != YY_END_OF_BUFFER_CHAR || base[size-1] != YY_END_OF_BUFFER_CHAR ) @@ -2465,31 +2479,31 @@ YY_BUFFER_STATE dcyy_scan_buffer (char * base, yy_size_t size ) /** Setup the input buffer state to scan a string. The next call to dcyylex() will * scan from a @e copy of @a str. * @param yystr a NUL-terminated string to scan - * + * * @return the newly allocated buffer state object. * @note If you want to scan bytes that may contain NUL values, then use * dcyy_scan_bytes() instead. */ YY_BUFFER_STATE dcyy_scan_string (yyconst char * yystr ) { - + return dcyy_scan_bytes(yystr,strlen(yystr) ); } /** Setup the input buffer state to scan the given bytes. The next call to dcyylex() will * scan from a @e copy of @a bytes. - * @param bytes the byte buffer to scan - * @param len the number of bytes in the buffer pointed to by @a bytes. - * + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * * @return the newly allocated buffer state object. */ -YY_BUFFER_STATE dcyy_scan_bytes (yyconst char * yybytes, int _yybytes_len ) +YY_BUFFER_STATE dcyy_scan_bytes (yyconst char * yybytes, yy_size_t _yybytes_len ) { YY_BUFFER_STATE b; char *buf; yy_size_t n; - int i; - + yy_size_t i; + /* Get memory for full buffer, including space for trailing EOB's. */ n = _yybytes_len + 2; buf = (char *) dcyyalloc(n ); @@ -2543,16 +2557,16 @@ static void yy_fatal_error (yyconst char* msg ) /* Accessor methods (get/set functions) to struct members. */ /** Get the current line number. - * + * */ int dcyyget_lineno (void) { - + return dcyylineno; } /** Get the input stream. - * + * */ FILE *dcyyget_in (void) { @@ -2560,7 +2574,7 @@ FILE *dcyyget_in (void) } /** Get the output stream. - * + * */ FILE *dcyyget_out (void) { @@ -2568,15 +2582,15 @@ FILE *dcyyget_out (void) } /** Get the length of the current token. - * + * */ -int dcyyget_leng (void) +yy_size_t dcyyget_leng (void) { return dcyyleng; } /** Get the current token. - * + * */ char *dcyyget_text (void) @@ -2586,18 +2600,18 @@ char *dcyyget_text (void) /** Set the current line number. * @param line_number - * + * */ void dcyyset_lineno (int line_number ) { - + dcyylineno = line_number; } /** Set the input stream. This does not discard the current * input buffer. * @param in_str A readable stream. - * + * * @see dcyy_switch_to_buffer */ void dcyyset_in (FILE * in_str ) @@ -2651,7 +2665,7 @@ static int yy_init_globals (void) /* dcyylex_destroy is for both reentrant and non-reentrant scanners. */ int dcyylex_destroy (void) { - + /* Pop the buffer stack, destroying each element. */ while(YY_CURRENT_BUFFER){ dcyy_delete_buffer(YY_CURRENT_BUFFER ); @@ -2677,7 +2691,7 @@ int dcyylex_destroy (void) #ifndef yytext_ptr static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) { - register int i; + int i; for ( i = 0; i < n; ++i ) s1[i] = s2[i]; } @@ -2686,7 +2700,7 @@ static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) #ifdef YY_NEED_STRLEN static int yy_flex_strlen (yyconst char * s ) { - register int n; + int n; for ( n = 0; s[n]; ++n ) ; @@ -2718,4 +2732,4 @@ void dcyyfree (void * ptr ) #define YYTABLES_NAME "yytables" -#line 753 "dcLexer.lxx" +#line 757 "direct/src/dcparser/dcLexer.lxx" diff --git a/direct/src/dcparser/dcLexer.lxx b/direct/src/dcparser/dcLexer.lxx index 3c20a69b5f..3a8156a4f3 100644 --- a/direct/src/dcparser/dcLexer.lxx +++ b/direct/src/dcparser/dcLexer.lxx @@ -1,7 +1,7 @@ /* // Filename: dcLexer.lxx // Created by: drose (05Oct00) -// +// //////////////////////////////////////////////////////////////////// */ @@ -103,12 +103,12 @@ dcyyerror(const std::string &msg) { if (!dc_filename.empty()) { cerr << " in " << dc_filename; } - cerr + cerr << " at line " << line_number << ", column " << col_number << ":\n" << current_line << "\n"; - indent(cerr, col_number-1) + indent(cerr, col_number-1) << "^\n" << msg << "\n\n"; - + error_count++; } @@ -120,10 +120,10 @@ dcyywarning(const std::string &msg) { if (!dc_filename.empty()) { cerr << " in " << dc_filename; } - cerr + cerr << " at line " << line_number << ", column " << col_number << ":\n" << current_line << "\n"; - indent(cerr, col_number-1) + indent(cerr, col_number-1) << "^\n" << msg << "\n\n"; warning_count++; @@ -313,9 +313,9 @@ scan_quoted_string(char quote_mark) { // scan_hex_string reads a string of hexadecimal digits delimited by // angle brackets and returns the representative string. -static std::string +static vector_uchar scan_hex_string() { - std::string result; + vector_uchar result; // We don't touch the current line number and column number during // scanning, so that if we detect an error while scanning the string @@ -345,24 +345,24 @@ scan_hex_string() { line_number = line; col_number = col; dcyyerror("Invalid hex digit."); - return std::string(); + return vector_uchar(); } odd = !odd; if (odd) { last = value; } else { - result += (char)((last << 4) | value); + result.push_back((unsigned char)((last << 4) | value)); } c = read_char(line, col); } if (c == EOF) { dcyyerror("This hex string is unterminated."); - return std::string(); + return vector_uchar(); } else if (odd) { dcyyerror("Odd number of hex digits."); - return std::string(); + return vector_uchar(); } line_number = line; @@ -382,7 +382,7 @@ eat_c_comment() { int col = col_number; int c, last_c; - + last_c = '\0'; c = read_char(line, col); while (c != EOF && !(last_c == '*' && c == '/')) { @@ -440,12 +440,12 @@ REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) yyless(1); } -[ \t\r] { +[ \t\r] { // Eat whitespace. accept(); } -"//".* { +"//".* { // Eat C++-style comments. accept(); } @@ -453,7 +453,7 @@ REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) "/*" { // Eat C-style comments. accept(); - eat_c_comment(); + eat_c_comment(); } @@ -607,7 +607,7 @@ REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) return KW_CHAR; } -{UNSIGNED_INTEGERNUM} { +{UNSIGNED_INTEGERNUM} { // An unsigned integer number. accept(); @@ -626,11 +626,11 @@ REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) dcyylval.u.uint64 = next_value + (*p - '0'); ++p; } - + return UNSIGNED_INTEGER; } -{SIGNED_INTEGERNUM} { +{SIGNED_INTEGERNUM} { // A signed integer number. accept(); @@ -671,14 +671,14 @@ REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) dcyyerror("Number out of range."); dcyylval.u.int64 = 1; } - } - + } + return SIGNED_INTEGER; } {UNSIGNED_HEXNUM} { // A hexadecimal integer number. - accept(); + accept(); // As above, we'll decode the hex string by hand. dcyylval.str = dcyytext; @@ -700,15 +700,15 @@ REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) ++p; } - return UNSIGNED_INTEGER; + return UNSIGNED_INTEGER; } -{REALNUM} { +{REALNUM} { // A floating-point number. - accept(); - dcyylval.u.real = patof(dcyytext); + accept(); + dcyylval.u.real = patof(dcyytext); dcyylval.str = dcyytext; - return REAL; + return REAL; } ["] { @@ -728,11 +728,11 @@ REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) [<] { // Long hex string. accept(); - dcyylval.str = scan_hex_string(); + dcyylval.bytes = scan_hex_string(); return HEX_STRING; } -[A-Za-z_][A-Za-z_0-9]* { +[A-Za-z_][A-Za-z_0-9]* { // Identifier or keyword. accept(); dcyylval.str = dcyytext; @@ -750,7 +750,7 @@ REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) . { // Send any other printable character as itself. - accept(); + accept(); return dcyytext[0]; } - + diff --git a/direct/src/dcparser/dcPacker.I b/direct/src/dcparser/dcPacker.I index 8c640d77b1..6b6ef79ee5 100644 --- a/direct/src/dcparser/dcPacker.I +++ b/direct/src/dcparser/dcPacker.I @@ -217,16 +217,30 @@ pack_string(const std::string &value) { } /** - * Adds the indicated string value into the stream, representing a single pre- - * packed field element, or a whole group of field elements at once. + * Packs the indicated numeric or string value into the stream. */ INLINE void DCPacker:: -pack_literal_value(const std::string &value) { +pack_blob(const vector_uchar &value) { nassertv(_mode == M_pack || _mode == M_repack); if (_current_field == nullptr) { _pack_error = true; } else { - _pack_data.append_data(value.data(), value.length()); + _current_field->pack_blob(_pack_data, value, _pack_error, _range_error); + advance(); + } +} + +/** + * Adds the indicated string value into the stream, representing a single pre- + * packed field element, or a whole group of field elements at once. + */ +INLINE void DCPacker:: +pack_literal_value(const vector_uchar &value) { + nassertv(_mode == M_pack || _mode == M_repack); + if (_current_field == nullptr) { + _pack_error = true; + } else { + _pack_data.append_data((const char *)value.data(), value.size()); advance(); } } @@ -345,16 +359,36 @@ unpack_string() { return value; } +/** + * Unpacks the current binary data value from the stream. + */ +INLINE vector_uchar DCPacker:: +unpack_blob() { + vector_uchar value; + nassertr(_mode == M_unpack, value); + if (_current_field == nullptr) { + _pack_error = true; + + } else { + _current_field->unpack_blob(_unpack_data, _unpack_length, _unpack_p, + value, _pack_error, _range_error); + advance(); + } + + return value; +} + /** * Returns the literal string that represents the packed value of the current * field, and advances the field pointer. */ -INLINE std::string DCPacker:: +INLINE vector_uchar DCPacker:: unpack_literal_value() { size_t start = _unpack_p; unpack_skip(); - nassertr(_unpack_p >= start, std::string()); - return std::string(_unpack_data + start, _unpack_p - start); + nassertr(_unpack_p >= start, vector_uchar()); + return vector_uchar((const unsigned char *)_unpack_data + start, + (const unsigned char *)_unpack_data + _unpack_p); } /** @@ -453,16 +487,33 @@ unpack_string(std::string &value) { } } +/** + * Unpacks the current numeric or string value from the stream. + */ +INLINE void DCPacker:: +unpack_blob(vector_uchar &value) { + nassertv(_mode == M_unpack); + if (_current_field == nullptr) { + _pack_error = true; + + } else { + _current_field->unpack_blob(_unpack_data, _unpack_length, _unpack_p, + value, _pack_error, _range_error); + advance(); + } +} + /** * Returns the literal string that represents the packed value of the current * field, and advances the field pointer. */ INLINE void DCPacker:: -unpack_literal_value(std::string &value) { +unpack_literal_value(vector_uchar &value) { size_t start = _unpack_p; unpack_skip(); nassertv(_unpack_p >= start); - value.assign(_unpack_data + start, _unpack_p - start); + value = vector_uchar((const unsigned char *)_unpack_data + start, + (const unsigned char *)_unpack_data + _unpack_p); } /** @@ -541,6 +592,15 @@ get_string() const { return _pack_data.get_string(); } +/** + * Returns the packed data buffer as a bytes object. Also see get_data(). + */ +INLINE vector_uchar DCPacker:: +get_bytes() const { + const unsigned char *p = (const unsigned char *)_pack_data.get_data(); + return vector_uchar(p, p + _pack_data.get_length()); +} + /** * Returns the total number of bytes in the unpack data buffer. This is the * buffer used when unpacking; it is separate from the pack data returned by @@ -601,9 +661,9 @@ take_data() { * between packing sessions. */ INLINE void DCPacker:: -append_data(const char *buffer, size_t size) { +append_data(const unsigned char *buffer, size_t size) { nassertv(_mode == M_idle); - _pack_data.append_data(buffer, size); + _pack_data.append_data((const char *)buffer, size); } /** @@ -728,6 +788,16 @@ raw_pack_string(const std::string &value) { _pack_data.append_data(value.data(), value.length()); } +/** + * Packs the data into the buffer between packing sessions. + */ +INLINE void DCPacker:: +raw_pack_blob(const vector_uchar &value) { + nassertv(_mode == M_idle); + DCPackerInterface::do_pack_uint16(_pack_data.get_write_pointer(2), value.size()); + _pack_data.append_data((const char *)value.data(), value.size()); +} + /** * Unpacks the data from the buffer between unpacking sessions. */ @@ -870,6 +940,16 @@ raw_unpack_string() { return value; } +/** + * Unpacks the data from the buffer between unpacking sessions. + */ +INLINE vector_uchar DCPacker:: +raw_unpack_blob() { + vector_uchar value; + raw_unpack_blob(value); + return value; +} + /** * Unpacks the data from the buffer between unpacking sessions. */ @@ -971,6 +1051,24 @@ raw_unpack_string(std::string &value) { _unpack_p += string_length; } +/** + * Unpacks the data from the buffer between unpacking sessions. + */ +INLINE void DCPacker:: +raw_unpack_blob(vector_uchar &value) { + nassertv(_mode == M_idle && _unpack_data != nullptr); + unsigned int blob_size = raw_unpack_uint16(); + + if (_unpack_p + blob_size > _unpack_length) { + _pack_error = true; + return; + } + + const unsigned char *p = (const unsigned char *)_unpack_data + _unpack_p; + value = vector_uchar(p, p + blob_size); + _unpack_p += blob_size; +} + /** * Advances to the next field after a call to pack_value() or pop(). */ diff --git a/direct/src/dcparser/dcPacker.cxx b/direct/src/dcparser/dcPacker.cxx index ebab348f6f..b6dbd60855 100644 --- a/direct/src/dcparser/dcPacker.cxx +++ b/direct/src/dcparser/dcPacker.cxx @@ -114,12 +114,12 @@ end_pack() { * version of begin_unpack() that takes only one parameter. */ void DCPacker:: -set_unpack_data(const string &data) { +set_unpack_data(const vector_uchar &data) { nassertv(_mode == M_idle); - char *buffer = new char[data.length()]; - memcpy(buffer, data.data(), data.length()); - set_unpack_data(buffer, data.length(), true); + char *buffer = new char[data.size()]; + memcpy(buffer, data.data(), data.size()); + set_unpack_data(buffer, data.size(), true); } /** @@ -721,11 +721,11 @@ pack_object(PyObject *object) { pack_string(string(buffer, length)); } } else if (PyBytes_Check(object)) { - char *buffer; + const unsigned char *buffer; Py_ssize_t length; - PyBytes_AsStringAndSize(object, &buffer, &length); + PyBytes_AsStringAndSize(object, (char **)&buffer, &length); if (buffer) { - pack_string(string(buffer, length)); + pack_blob(vector_uchar(buffer, buffer + length)); } #else } else if (PyString_Check(object) || PyUnicode_Check(object)) { @@ -1118,9 +1118,9 @@ enquote_string(ostream &out, char quote_mark, const string &str) { * Outputs the indicated string as a hex constant. */ void DCPacker:: -output_hex_string(ostream &out, const string &str) { +output_hex_string(ostream &out, const vector_uchar &str) { out << '<'; - for (string::const_iterator pi = str.begin(); + for (vector_uchar::const_iterator pi = str.begin(); pi != str.end(); ++pi) { char buffer[10]; diff --git a/direct/src/dcparser/dcPacker.h b/direct/src/dcparser/dcPacker.h index 68a01a5999..dce28dbc30 100644 --- a/direct/src/dcparser/dcPacker.h +++ b/direct/src/dcparser/dcPacker.h @@ -41,7 +41,7 @@ PUBLISHED: void begin_pack(const DCPackerInterface *root); bool end_pack(); - void set_unpack_data(const std::string &data); + void set_unpack_data(const vector_uchar &data); public: void set_unpack_data(const char *unpack_data, size_t unpack_length, bool owns_unpack_data); @@ -75,7 +75,8 @@ PUBLISHED: INLINE void pack_int64(int64_t value); INLINE void pack_uint64(uint64_t value); INLINE void pack_string(const std::string &value); - INLINE void pack_literal_value(const std::string &value); + INLINE void pack_blob(const vector_uchar &value); + INLINE void pack_literal_value(const vector_uchar &value); void pack_default_value(); INLINE double unpack_double(); @@ -84,7 +85,8 @@ PUBLISHED: INLINE int64_t unpack_int64(); INLINE uint64_t unpack_uint64(); INLINE std::string unpack_string(); - INLINE std::string unpack_literal_value(); + INLINE vector_uchar unpack_blob(); + INLINE vector_uchar unpack_literal_value(); void unpack_validate(); void unpack_skip(); @@ -97,7 +99,8 @@ public: INLINE void unpack_int64(int64_t &value); INLINE void unpack_uint64(uint64_t &value); INLINE void unpack_string(std::string &value); - INLINE void unpack_literal_value(std::string &value); + INLINE void unpack_blob(vector_uchar &value); + INLINE void unpack_literal_value(vector_uchar &value); PUBLISHED: @@ -119,6 +122,7 @@ PUBLISHED: INLINE size_t get_length() const; INLINE std::string get_string() const; + INLINE vector_uchar get_bytes() const; INLINE size_t get_unpack_length() const; INLINE std::string get_unpack_string() const; public: @@ -126,7 +130,7 @@ public: INLINE const char *get_data() const; INLINE char *take_data(); - INLINE void append_data(const char *buffer, size_t size); + INLINE void append_data(const unsigned char *buffer, size_t size); INLINE char *get_write_pointer(size_t size); INLINE const char *get_unpack_data() const; @@ -148,6 +152,7 @@ PUBLISHED: INLINE void raw_pack_uint64(uint64_t value); INLINE void raw_pack_float64(double value); INLINE void raw_pack_string(const std::string &value); + INLINE void raw_pack_blob(const vector_uchar &value); // this is a hack to allw me to get in and out of 32bit Mode Faster need to // agree with channel_type in dcbase.h @@ -165,6 +170,7 @@ PUBLISHED: INLINE uint64_t raw_unpack_uint64(); INLINE double raw_unpack_float64(); INLINE std::string raw_unpack_string(); + INLINE vector_uchar raw_unpack_blob(); public: INLINE void raw_unpack_int8(int &value); @@ -177,10 +183,11 @@ public: INLINE void raw_unpack_uint64(uint64_t &value); INLINE void raw_unpack_float64(double &value); INLINE void raw_unpack_string(std::string &value); + INLINE void raw_unpack_blob(vector_uchar &value); public: static void enquote_string(std::ostream &out, char quote_mark, const std::string &str); - static void output_hex_string(std::ostream &out, const std::string &str); + static void output_hex_string(std::ostream &out, const vector_uchar &str); private: INLINE void advance(); diff --git a/direct/src/dcparser/dcPackerInterface.cxx b/direct/src/dcparser/dcPackerInterface.cxx index d17ff1f7b7..db51d2712c 100644 --- a/direct/src/dcparser/dcPackerInterface.cxx +++ b/direct/src/dcparser/dcPackerInterface.cxx @@ -248,6 +248,14 @@ pack_string(DCPackData &, const string &, bool &pack_error, bool &) const { pack_error = true; } +/** + * Packs the indicated numeric or string value into the stream. + */ +void DCPackerInterface:: +pack_blob(DCPackData &, const vector_uchar &, bool &pack_error, bool &) const { + pack_error = true; +} + /** * Packs the field's specified default value (or a sensible default if no * value is specified) into the stream. Returns true if the default value is @@ -306,6 +314,14 @@ unpack_string(const char *, size_t, size_t &, string &, bool &pack_error, bool & pack_error = true; } +/** + * Unpacks the current numeric or string value from the stream. + */ +void DCPackerInterface:: +unpack_blob(const char *, size_t, size_t &, vector_uchar &, bool &pack_error, bool &) const { + pack_error = true; +} + /** * Internally unpacks the current numeric or string value and validates it * against the type range limits, but does not return the value. Returns true diff --git a/direct/src/dcparser/dcPackerInterface.h b/direct/src/dcparser/dcPackerInterface.h index ef28b85647..0b2f81d2fe 100644 --- a/direct/src/dcparser/dcPackerInterface.h +++ b/direct/src/dcparser/dcPackerInterface.h @@ -16,6 +16,7 @@ #include "dcbase.h" #include "dcSubatomicType.h" +#include "vector_uchar.h" class DCFile; class DCField; @@ -37,8 +38,8 @@ enum DCPackType { // These PackTypes are all fundamental types, and should be packed (or // unpacked) with the corresponding call to pack_double(), pack_int(), etc. - // PT_blob is the same as PT_string, but implies that the string contains - // binary data. + // PT_blob is similar to PT_string, except that it contains arbitrary binary + // data instead of just UTF-8 text. PT_double, PT_int, PT_uint, @@ -113,6 +114,8 @@ public: bool &pack_error, bool &range_error) const; virtual void pack_string(DCPackData &pack_data, const std::string &value, bool &pack_error, bool &range_error) const; + virtual void pack_blob(DCPackData &pack_data, const vector_uchar &value, + bool &pack_error, bool &range_error) const; virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const; virtual void unpack_double(const char *data, size_t length, size_t &p, @@ -127,6 +130,8 @@ public: uint64_t &value, bool &pack_error, bool &range_error) const; virtual void unpack_string(const char *data, size_t length, size_t &p, std::string &value, bool &pack_error, bool &range_error) const; + virtual void unpack_blob(const char *data, size_t length, size_t &p, + vector_uchar &value, bool &pack_error, bool &range_error) const; virtual bool unpack_validate(const char *data, size_t length, size_t &p, bool &pack_error, bool &range_error) const; virtual bool unpack_skip(const char *data, size_t length, size_t &p, diff --git a/direct/src/dcparser/dcParser.cxx.prebuilt b/direct/src/dcparser/dcParser.cxx.prebuilt index 80df4b2b09..9adc570263 100644 --- a/direct/src/dcparser/dcParser.cxx.prebuilt +++ b/direct/src/dcparser/dcParser.cxx.prebuilt @@ -1,20 +1,19 @@ -/* A Bison parser, made by GNU Bison 2.4.2. */ +/* A Bison parser, made by GNU Bison 3.0.4. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. -/* Skeleton implementation for Bison's Yacc-like parsers in C - - Copyright (C) 1984, 1989-1990, 2000-2006, 2009-2010 Free Software - Foundation, Inc. - This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program. If not, see . */ @@ -27,7 +26,7 @@ special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. - + This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ @@ -45,7 +44,7 @@ #define YYBISON 1 /* Bison version. */ -#define YYBISON_VERSION "2.4.2" +#define YYBISON_VERSION "3.0.4" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" @@ -59,23 +58,19 @@ /* Pull parsers. */ #define YYPULL 1 -/* Using locations. */ -#define YYLSP_NEEDED 0 /* Substitute the variable and function names. */ #define yyparse dcyyparse #define yylex dcyylex #define yyerror dcyyerror -#define yylval dcyylval -#define yychar dcyychar #define yydebug dcyydebug #define yynerrs dcyynerrs +#define yylval dcyylval +#define yychar dcyychar /* Copy the first part of user declarations. */ - -/* Line 189 of yacc.c */ -#line 6 "dcParser.yxx" +#line 7 "direct/src/dcparser/dcParser.yxx" /* yacc.c:339 */ #include "dcLexerDefs.h" #include "dcParserDefs.h" @@ -105,18 +100,18 @@ using std::istream; using std::ostringstream; using std::string; -DCFile *dc_file = (DCFile *)NULL; -static DCClass *current_class = (DCClass *)NULL; -static DCSwitch *current_switch = (DCSwitch *)NULL; -static DCAtomicField *current_atomic = (DCAtomicField *)NULL; -static DCMolecularField *current_molecular = (DCMolecularField *)NULL; -static DCParameter *current_parameter = (DCParameter *)NULL; +DCFile *dc_file = nullptr; +static DCClass *current_class = nullptr; +static DCSwitch *current_switch = nullptr; +static DCAtomicField *current_atomic = nullptr; +static DCMolecularField *current_molecular = nullptr; +static DCParameter *current_parameter = nullptr; static DCKeywordList current_keyword_list; static DCPacker default_packer; static DCPacker *current_packer; static DCDoubleRange double_range; static DCUnsignedIntRange uint_range; -static DCField *parameter_description = (DCField *)NULL; +static DCField *parameter_description = nullptr; //////////////////////////////////////////////////////////////////// // Defining the interface to the parser. @@ -129,9 +124,9 @@ dc_init_parser(istream &in, const string &filename, DCFile &file) { } void -dc_init_parser_parameter_value(istream &in, const string &filename, +dc_init_parser_parameter_value(istream &in, const string &filename, DCPacker &packer) { - dc_file = NULL; + dc_file = nullptr; current_packer = &packer; dc_init_lexer(in, filename); dc_start_parameter_value(); @@ -142,7 +137,7 @@ dc_init_parser_parameter_description(istream &in, const string &filename, DCFile *file) { dc_file = file; dc_init_lexer(in, filename); - parameter_description = NULL; + parameter_description = nullptr; dc_start_parameter_description(); } @@ -153,18 +148,19 @@ dc_get_parameter_description() { void dc_cleanup_parser() { - dc_file = (DCFile *)NULL; + dc_file = nullptr; } +#line 156 "built/tmp/dcParser.yxx.c" /* yacc.c:339 */ -/* Line 189 of yacc.c */ -#line 159 "y.tab.c" - -/* Enabling traces. */ -#ifndef YYDEBUG -# define YYDEBUG 0 -#endif +# ifndef YY_NULLPTR +# if defined __cplusplus && 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# endif /* Enabling verbose error messages. */ #ifdef YYERROR_VERBOSE @@ -174,67 +170,72 @@ dc_cleanup_parser() { # define YYERROR_VERBOSE 0 #endif -/* Enabling the token table. */ -#ifndef YYTOKEN_TABLE -# define YYTOKEN_TABLE 0 +/* In a future release of Bison, this section will be replaced + by #include "dcParser.yxx.h". */ +#ifndef YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED +# define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int dcyydebug; #endif - -/* Tokens. */ +/* Token type. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE - /* Put the tokens into the symbol table, so that GDB and other debuggers - know about them. */ - enum yytokentype { - UNSIGNED_INTEGER = 258, - SIGNED_INTEGER = 259, - REAL = 260, - STRING = 261, - HEX_STRING = 262, - IDENTIFIER = 263, - KEYWORD = 264, - KW_DCLASS = 265, - KW_STRUCT = 266, - KW_FROM = 267, - KW_IMPORT = 268, - KW_TYPEDEF = 269, - KW_KEYWORD = 270, - KW_SWITCH = 271, - KW_CASE = 272, - KW_DEFAULT = 273, - KW_BREAK = 274, - KW_INT8 = 275, - KW_INT16 = 276, - KW_INT32 = 277, - KW_INT64 = 278, - KW_UINT8 = 279, - KW_UINT16 = 280, - KW_UINT32 = 281, - KW_UINT64 = 282, - KW_FLOAT64 = 283, - KW_STRING = 284, - KW_BLOB = 285, - KW_BLOB32 = 286, - KW_INT8ARRAY = 287, - KW_INT16ARRAY = 288, - KW_INT32ARRAY = 289, - KW_UINT8ARRAY = 290, - KW_UINT16ARRAY = 291, - KW_UINT32ARRAY = 292, - KW_UINT32UINT8ARRAY = 293, - KW_CHAR = 294, - START_DC = 295, - START_PARAMETER_VALUE = 296, - START_PARAMETER_DESCRIPTION = 297 - }; + enum yytokentype + { + UNSIGNED_INTEGER = 258, + SIGNED_INTEGER = 259, + REAL = 260, + STRING = 261, + IDENTIFIER = 262, + HEX_STRING = 263, + KEYWORD = 264, + KW_DCLASS = 265, + KW_STRUCT = 266, + KW_FROM = 267, + KW_IMPORT = 268, + KW_TYPEDEF = 269, + KW_KEYWORD = 270, + KW_SWITCH = 271, + KW_CASE = 272, + KW_DEFAULT = 273, + KW_BREAK = 274, + KW_INT8 = 275, + KW_INT16 = 276, + KW_INT32 = 277, + KW_INT64 = 278, + KW_UINT8 = 279, + KW_UINT16 = 280, + KW_UINT32 = 281, + KW_UINT64 = 282, + KW_FLOAT64 = 283, + KW_STRING = 284, + KW_BLOB = 285, + KW_BLOB32 = 286, + KW_INT8ARRAY = 287, + KW_INT16ARRAY = 288, + KW_INT32ARRAY = 289, + KW_UINT8ARRAY = 290, + KW_UINT16ARRAY = 291, + KW_UINT32ARRAY = 292, + KW_UINT32UINT8ARRAY = 293, + KW_CHAR = 294, + START_DC = 295, + START_PARAMETER_VALUE = 296, + START_PARAMETER_DESCRIPTION = 297 + }; #endif /* Tokens. */ #define UNSIGNED_INTEGER 258 #define SIGNED_INTEGER 259 #define REAL 260 #define STRING 261 -#define HEX_STRING 262 -#define IDENTIFIER 263 +#define IDENTIFIER 262 +#define HEX_STRING 263 #define KEYWORD 264 #define KW_DCLASS 265 #define KW_STRUCT 266 @@ -270,21 +271,18 @@ dc_cleanup_parser() { #define START_PARAMETER_VALUE 296 #define START_PARAMETER_DESCRIPTION 297 +/* Value type. */ +extern YYSTYPE dcyylval; -#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED - -# define yystype YYSTYPE /* obsolescent; will be withdrawn */ -# define YYSTYPE_IS_DECLARED 1 -#endif +int dcyyparse (void); +#endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED */ /* Copy the second part of user declarations. */ - -/* Line 264 of yacc.c */ -#line 284 "y.tab.c" +#line 286 "built/tmp/dcParser.yxx.c" /* yacc.c:358 */ #ifdef short # undef short @@ -298,11 +296,8 @@ typedef unsigned char yytype_uint8; #ifdef YYTYPE_INT8 typedef YYTYPE_INT8 yytype_int8; -#elif (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) -typedef signed char yytype_int8; #else -typedef short int yytype_int8; +typedef signed char yytype_int8; #endif #ifdef YYTYPE_UINT16 @@ -322,8 +317,7 @@ typedef short int yytype_int16; # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t -# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) +# elif ! defined YYSIZE_T # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else @@ -337,38 +331,67 @@ typedef short int yytype_int16; # if defined YYENABLE_NLS && YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ -# define YY_(msgid) dgettext ("bison-runtime", msgid) +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) # endif # endif # ifndef YY_ -# define YY_(msgid) msgid +# define YY_(Msgid) Msgid +# endif +#endif + +#ifndef YY_ATTRIBUTE +# if (defined __GNUC__ \ + && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \ + || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C +# define YY_ATTRIBUTE(Spec) __attribute__(Spec) +# else +# define YY_ATTRIBUTE(Spec) /* empty */ +# endif +#endif + +#ifndef YY_ATTRIBUTE_PURE +# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__)) +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__)) +#endif + +#if !defined _Noreturn \ + && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112) +# if defined _MSC_VER && 1200 <= _MSC_VER +# define _Noreturn __declspec (noreturn) +# else +# define _Noreturn YY_ATTRIBUTE ((__noreturn__)) # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ -# define YYUSE(e) ((void) (e)) +# define YYUSE(E) ((void) (E)) #else -# define YYUSE(e) /* empty */ +# define YYUSE(E) /* empty */ #endif -/* Identity function, used to suppress warnings about constant conditions. */ -#ifndef lint -# define YYID(n) (n) +#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") #else -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) -static int -YYID (int yyi) -#else -static int -YYID (yyi) - int yyi; +# define YY_INITIAL_VALUE(Value) Value #endif -{ - return yyi; -} +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END #endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + #if ! defined yyoverflow || YYERROR_VERBOSE @@ -387,11 +410,11 @@ YYID (yyi) # define alloca _alloca # else # define YYSTACK_ALLOC alloca -# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS # include /* INFRINGES ON USER NAME SPACE */ -# ifndef _STDLIB_H -# define _STDLIB_H 1 + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 # endif # endif # endif @@ -399,8 +422,8 @@ YYID (yyi) # endif # ifdef YYSTACK_ALLOC - /* Pacify GCC's `empty if-body' warning. */ -# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) # ifndef YYSTACK_ALLOC_MAXIMUM /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely @@ -414,25 +437,23 @@ YYID (yyi) # ifndef YYSTACK_ALLOC_MAXIMUM # define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM # endif -# if (defined __cplusplus && ! defined _STDLIB_H \ +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ && ! ((defined YYMALLOC || defined malloc) \ - && (defined YYFREE || defined free))) + && (defined YYFREE || defined free))) # include /* INFRINGES ON USER NAME SPACE */ -# ifndef _STDLIB_H -# define _STDLIB_H 1 +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 # endif # endif # ifndef YYMALLOC # define YYMALLOC malloc -# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) +# if ! defined malloc && ! defined EXIT_SUCCESS void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ # endif # endif # ifndef YYFREE # define YYFREE free -# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) +# if ! defined free && ! defined EXIT_SUCCESS void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif @@ -442,7 +463,7 @@ void free (void *); /* INFRINGES ON USER NAME SPACE */ #if (! defined yyoverflow \ && (! defined __cplusplus \ - || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc @@ -460,46 +481,50 @@ union yyalloc ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + YYSTACK_GAP_MAXIMUM) -/* Copy COUNT objects from FROM to TO. The source and destination do - not overlap. */ -# ifndef YYCOPY -# if defined __GNUC__ && 1 < __GNUC__ -# define YYCOPY(To, From, Count) \ - __builtin_memcpy (To, From, (Count) * sizeof (*(From))) -# else -# define YYCOPY(To, From, Count) \ - do \ - { \ - YYSIZE_T yyi; \ - for (yyi = 0; yyi < (Count); yyi++) \ - (To)[yyi] = (From)[yyi]; \ - } \ - while (YYID (0)) -# endif -# endif +# define YYCOPY_NEEDED 1 /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ -# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ - do \ - { \ - YYSIZE_T yynewbytes; \ - YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ - Stack = &yyptr->Stack_alloc; \ - yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ - yyptr += yynewbytes / sizeof (*yyptr); \ - } \ - while (YYID (0)) +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (0) #endif +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + /* YYFINAL -- State number of the termination state. */ #define YYFINAL 57 /* YYLAST -- Last index in YYTABLE. */ -#define YYLAST 430 +#define YYLAST 431 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 58 @@ -507,17 +532,19 @@ union yyalloc #define YYNNTS 82 /* YYNRULES -- Number of rules. */ #define YYNRULES 190 -/* YYNRULES -- Number of states. */ +/* YYNSTATES -- Number of states. */ #define YYNSTATES 282 -/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned + by yylex, with out-of-bounds checking. */ #define YYUNDEFTOK 2 #define YYMAXUTOK 297 -#define YYTRANSLATE(YYX) \ +#define YYTRANSLATE(YYX) \ ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) -/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, without out-of-bounds checking. */ static const yytype_uint8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -553,123 +580,39 @@ static const yytype_uint8 yytranslate[] = }; #if YYDEBUG -/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in - YYRHS. */ -static const yytype_uint16 yyprhs[] = -{ - 0, 0, 3, 6, 9, 12, 14, 17, 20, 23, - 26, 29, 32, 34, 38, 40, 44, 47, 48, 54, - 56, 58, 60, 64, 67, 70, 72, 75, 78, 80, - 82, 83, 91, 93, 95, 98, 100, 104, 106, 109, - 113, 116, 119, 122, 125, 126, 134, 136, 138, 141, - 143, 147, 149, 152, 156, 159, 162, 165, 168, 169, - 175, 177, 179, 181, 185, 187, 188, 192, 194, 196, - 197, 202, 204, 205, 210, 212, 214, 216, 218, 220, - 222, 225, 228, 231, 233, 238, 242, 246, 248, 250, - 252, 254, 256, 258, 262, 265, 269, 275, 280, 282, - 284, 288, 291, 295, 301, 306, 308, 313, 315, 319, - 323, 328, 330, 332, 334, 336, 338, 340, 342, 344, - 346, 348, 350, 352, 353, 358, 360, 362, 364, 366, - 368, 369, 374, 375, 380, 381, 386, 390, 394, 398, - 402, 404, 407, 409, 411, 413, 417, 419, 421, 423, - 425, 427, 429, 431, 433, 435, 437, 439, 441, 443, - 445, 447, 449, 451, 453, 455, 457, 459, 462, 464, - 465, 470, 472, 474, 478, 480, 482, 483, 493, 495, - 498, 501, 504, 508, 512, 513, 518, 521, 523, 525, - 527 -}; - -/* YYRHS -- A `-1'-separated list of the rules' RHS. */ -static const yytype_int16 yyrhs[] = -{ - 59, 0, -1, 40, 60, -1, 41, 114, -1, 42, - 100, -1, 139, -1, 60, 43, -1, 60, 70, -1, - 60, 131, -1, 60, 63, -1, 60, 67, -1, 60, - 68, -1, 8, -1, 61, 44, 8, -1, 61, -1, - 62, 45, 61, -1, 13, 62, -1, -1, 12, 62, - 13, 64, 65, -1, 66, -1, 46, -1, 61, -1, - 66, 47, 61, -1, 14, 98, -1, 15, 69, -1, - 139, -1, 69, 8, -1, 69, 9, -1, 71, -1, - 78, -1, -1, 10, 130, 72, 74, 48, 76, 49, - -1, 8, -1, 139, -1, 50, 75, -1, 73, -1, - 75, 47, 73, -1, 139, -1, 76, 43, -1, 76, - 77, 43, -1, 85, 124, -1, 126, 125, -1, 95, - 124, -1, 93, 124, -1, -1, 11, 130, 79, 81, - 48, 83, 49, -1, 8, -1, 139, -1, 50, 82, - -1, 80, -1, 82, 47, 80, -1, 139, -1, 83, - 43, -1, 83, 84, 43, -1, 85, 125, -1, 126, - 125, -1, 95, 125, -1, 93, 125, -1, -1, 130, - 51, 86, 87, 52, -1, 139, -1, 88, -1, 89, - -1, 88, 47, 89, -1, 98, -1, -1, 105, 91, - 106, -1, 105, -1, 90, -1, -1, 90, 53, 94, - 114, -1, 92, -1, -1, 92, 53, 96, 114, -1, - 90, -1, 92, -1, 93, -1, 95, -1, 97, -1, - 85, -1, 85, 125, -1, 95, 125, -1, 93, 125, - -1, 123, -1, 101, 51, 103, 52, -1, 101, 44, - 108, -1, 101, 54, 112, -1, 101, -1, 8, -1, - 78, -1, 131, -1, 139, -1, 113, -1, 113, 55, - 113, -1, 113, 112, -1, 103, 47, 113, -1, 103, - 47, 113, 55, 113, -1, 103, 47, 113, 112, -1, - 139, -1, 107, -1, 107, 55, 107, -1, 107, 109, - -1, 104, 47, 107, -1, 104, 47, 107, 55, 107, - -1, 104, 47, 107, 109, -1, 102, -1, 105, 56, - 104, 57, -1, 8, -1, 106, 44, 108, -1, 106, - 54, 112, -1, 106, 56, 104, 57, -1, 6, -1, - 108, -1, 3, -1, 4, -1, 4, -1, 3, -1, - 111, -1, 110, -1, 5, -1, 6, -1, 112, -1, - 116, -1, -1, 8, 53, 115, 116, -1, 110, -1, - 111, -1, 5, -1, 6, -1, 7, -1, -1, 48, - 117, 120, 49, -1, -1, 56, 118, 120, 57, -1, - -1, 51, 119, 120, 52, -1, 110, 46, 108, -1, - 111, 46, 108, -1, 5, 46, 108, -1, 7, 46, - 108, -1, 121, -1, 122, 121, -1, 139, -1, 47, - -1, 114, -1, 122, 47, 114, -1, 20, -1, 21, - -1, 22, -1, 23, -1, 24, -1, 25, -1, 26, - -1, 27, -1, 28, -1, 29, -1, 30, -1, 31, - -1, 32, -1, 33, -1, 34, -1, 35, -1, 36, - -1, 37, -1, 38, -1, 39, -1, 139, -1, 124, - 9, -1, 124, -1, -1, 8, 50, 127, 129, -1, - 8, -1, 128, -1, 129, 47, 128, -1, 139, -1, - 8, -1, -1, 16, 130, 51, 99, 52, 48, 132, - 133, 49, -1, 139, -1, 133, 43, -1, 133, 134, - -1, 133, 136, -1, 133, 137, 43, -1, 133, 138, - 43, -1, -1, 17, 135, 114, 50, -1, 18, 50, - -1, 19, -1, 95, -1, 93, -1, -1 -}; - -/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_uint16 yyrline[] = { - 0, 168, 168, 169, 170, 177, 178, 179, 190, 196, - 197, 198, 202, 203, 210, 211, 218, 223, 222, 230, - 231, 238, 242, 249, 267, 271, 272, 276, 287, 288, - 293, 292, 304, 327, 328, 332, 338, 352, 353, 354, - 367, 377, 378, 386, 397, 396, 408, 431, 432, 436, - 442, 451, 452, 453, 464, 471, 472, 476, 484, 483, - 501, 502, 506, 507, 511, 521, 520, 531, 535, 537, - 536, 565, 567, 566, 595, 596, 600, 601, 605, 609, - 616, 620, 624, 631, 635, 644, 660, 675, 676, 708, - 724, 743, 747, 754, 761, 770, 776, 782, 793, 797, - 804, 811, 818, 824, 830, 839, 840, 851, 856, 871, - 886, 893, 902, 906, 917, 931, 935, 939, 943, 947, - 951, 960, 965, 969, 968, 983, 987, 991, 995, 999, - 1004, 1003, 1012, 1011, 1020, 1019, 1027, 1033, 1039, 1045, - 1054, 1055, 1059, 1060, 1064, 1065, 1069, 1073, 1077, 1081, - 1085, 1089, 1093, 1097, 1101, 1105, 1109, 1113, 1117, 1121, - 1125, 1129, 1133, 1137, 1141, 1145, 1152, 1156, 1163, 1173, - 1172, 1183, 1210, 1216, 1230, 1234, 1239, 1238, 1251, 1252, - 1253, 1254, 1255, 1256, 1270, 1269, 1290, 1299, 1306, 1310, - 1316 + 0, 174, 174, 175, 176, 183, 184, 185, 196, 202, + 203, 204, 208, 209, 216, 217, 224, 229, 228, 236, + 237, 244, 248, 255, 273, 277, 278, 282, 293, 294, + 299, 298, 310, 333, 334, 338, 344, 358, 359, 360, + 373, 383, 384, 392, 403, 402, 414, 437, 438, 442, + 448, 457, 458, 459, 470, 477, 478, 482, 490, 489, + 507, 508, 512, 513, 517, 527, 526, 537, 541, 543, + 542, 571, 573, 572, 601, 602, 606, 607, 611, 615, + 622, 626, 630, 637, 641, 650, 666, 681, 682, 714, + 730, 749, 753, 760, 767, 776, 782, 788, 799, 803, + 810, 817, 824, 830, 836, 845, 846, 857, 862, 877, + 892, 899, 908, 912, 923, 937, 941, 945, 949, 953, + 957, 966, 971, 975, 974, 989, 993, 997, 1001, 1005, + 1010, 1009, 1018, 1017, 1026, 1025, 1033, 1039, 1045, 1051, + 1060, 1061, 1065, 1066, 1070, 1071, 1075, 1079, 1083, 1087, + 1091, 1095, 1099, 1103, 1107, 1111, 1115, 1119, 1123, 1127, + 1131, 1135, 1139, 1143, 1147, 1151, 1158, 1162, 1169, 1179, + 1178, 1189, 1216, 1222, 1236, 1240, 1245, 1244, 1257, 1258, + 1259, 1260, 1261, 1262, 1276, 1275, 1296, 1305, 1312, 1316, + 1322 }; #endif -#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +#if YYDEBUG || YYERROR_VERBOSE || 0 /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { "$end", "error", "$undefined", "UNSIGNED_INTEGER", "SIGNED_INTEGER", - "REAL", "STRING", "HEX_STRING", "IDENTIFIER", "KEYWORD", "KW_DCLASS", + "REAL", "STRING", "IDENTIFIER", "HEX_STRING", "KEYWORD", "KW_DCLASS", "KW_STRUCT", "KW_FROM", "KW_IMPORT", "KW_TYPEDEF", "KW_KEYWORD", "KW_SWITCH", "KW_CASE", "KW_DEFAULT", "KW_BREAK", "KW_INT8", "KW_INT16", "KW_INT32", "KW_INT64", "KW_UINT8", "KW_UINT16", "KW_UINT32", @@ -697,13 +640,13 @@ static const char *const yytname[] = "array_def", "type_token", "keyword_list", "no_keyword_list", "molecular_field", "$@12", "atomic_name", "molecular_atom_list", "optional_name", "switch", "@13", "switch_fields", "switch_case", "$@14", - "switch_default", "switch_break", "switch_field", "empty", 0 + "switch_default", "switch_break", "switch_field", "empty", YY_NULLPTR }; #endif # ifdef YYPRINT -/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to - token YYLEX-NUM. */ +/* YYTOKNUM[NUM] -- (External) token number corresponding to the + (internal) symbol number NUM (which must be that of a token). */ static const yytype_uint16 yytoknum[] = { 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, @@ -715,7 +658,250 @@ static const yytype_uint16 yytoknum[] = }; # endif -/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +#define YYPACT_NINF -127 + +#define yypact_value_is_default(Yystate) \ + (!!((Yystate) == (-127))) + +#define YYTABLE_NINF -176 + +#define yytable_value_is_error(Yytable_value) \ + 0 + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int16 yypact[] = +{ + 61, -127, 104, 359, 19, 128, -127, -127, -127, -23, + -127, -27, 17, -127, -127, -127, 44, 69, -127, -127, + 15, 42, 42, -127, -127, -127, -127, -127, -127, -127, + -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, + -127, -127, -127, -127, -127, 46, 76, -127, -127, -127, + 4, -127, 27, -127, 22, -127, -127, -127, 42, 63, + 63, 392, -127, -127, -127, -127, -127, -127, -127, -127, + -127, 117, -127, 117, 24, 24, 24, 117, 117, -127, + -127, 96, 142, -127, -127, -127, -127, -127, -127, 117, + 153, 158, 53, 146, -127, -127, -127, 110, 1, 119, + -127, -127, -127, -127, 141, -127, -127, -127, 122, -127, + -127, -127, 116, -127, 120, -127, 114, 111, -127, -127, + 124, 359, -127, 104, 104, -127, -127, -127, 67, -127, + -127, -127, 33, -127, -127, -127, -12, 7, -127, -127, + -127, 20, 392, 125, 162, -127, 63, -127, -127, -127, + -127, 104, -127, -127, -127, 165, 129, -127, -127, -127, + -127, -127, 130, -127, -127, 153, -127, 153, -127, 53, + -127, -127, 53, -127, 117, 158, 53, 131, 133, -127, + -127, -127, 169, 136, -127, -127, 11, 110, -127, -127, + -127, 134, -127, 137, 36, -127, 12, -127, -127, -127, + -4, -127, 392, -127, -127, 139, -127, -127, 110, -127, + 140, 165, 291, -127, -127, 153, -127, 53, -127, -127, + -127, 169, 325, -127, 63, -127, 31, -127, -127, 145, + -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, + 151, -127, -127, -127, -127, 110, -127, -127, -127, -127, + -127, -127, 257, -127, -127, 142, 142, 142, -127, 182, + -127, 147, -127, -127, -127, -127, -127, -127, -127, 155, + 156, -127, -127, 149, 104, -127, -127, -127, 182, 150, + -127, -127 +}; + + /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 190, 0, 190, 0, 2, 5, 116, 115, 127, + 128, 0, 129, 130, 134, 132, 125, 126, 3, 122, + 88, 190, 190, 146, 147, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, + 163, 164, 165, 89, 190, 68, 71, 190, 190, 4, + 87, 105, 67, 83, 0, 90, 174, 1, 190, 0, + 0, 0, 190, 6, 9, 10, 11, 7, 28, 29, + 8, 0, 123, 0, 190, 190, 190, 0, 0, 175, + 44, 0, 168, 80, 166, 69, 72, 82, 81, 0, + 190, 0, 190, 0, 58, 30, 12, 14, 0, 16, + 88, 76, 77, 23, 24, 25, 113, 138, 0, 139, + 143, 144, 0, 140, 190, 142, 0, 0, 136, 137, + 190, 190, 167, 0, 0, 85, 119, 120, 0, 118, + 117, 121, 92, 91, 86, 111, 0, 99, 112, 98, + 107, 66, 190, 190, 0, 17, 0, 26, 27, 124, + 131, 143, 141, 135, 133, 0, 0, 47, 79, 74, + 75, 78, 0, 70, 73, 0, 84, 0, 94, 0, + 106, 114, 0, 101, 0, 0, 190, 0, 61, 62, + 64, 60, 0, 0, 33, 13, 0, 15, 145, 46, + 49, 48, 190, 0, 95, 93, 102, 100, 108, 109, + 0, 59, 0, 32, 35, 34, 190, 20, 21, 18, + 19, 0, 190, 51, 176, 0, 97, 0, 104, 110, + 63, 0, 190, 37, 0, 50, 88, 52, 45, 0, + 190, 190, 190, 190, 190, 96, 103, 36, 38, 31, + 0, 190, 190, 190, 190, 22, 169, 53, 54, 57, + 56, 55, 0, 178, 39, 40, 43, 42, 41, 0, + 184, 0, 187, 179, 177, 189, 188, 180, 181, 0, + 0, 171, 172, 170, 0, 186, 182, 183, 0, 0, + 173, 185 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int16 yypgoto[] = +{ + -127, -127, -127, -126, 143, -127, -127, -127, -127, -127, + -127, -127, -127, -127, -127, -20, -127, -127, -127, -127, + 197, -127, -6, -127, -127, -127, -127, -116, -127, -127, + -127, 5, 85, -127, 87, -1, -127, 0, -127, -127, + 152, -127, -127, -127, -127, -127, 34, -127, -127, -125, + -56, 18, -81, -78, -90, -115, -2, -127, 108, -127, + -127, -127, 60, 103, -127, -127, -110, -40, -3, -127, + -60, -127, 47, 215, -127, -127, -127, -127, -127, -127, + -127, 3 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int16 yydefgoto[] = +{ + -1, 4, 5, 97, 98, 64, 186, 209, 210, 65, + 66, 104, 67, 68, 143, 204, 183, 205, 222, 240, + 43, 120, 190, 156, 191, 212, 229, 44, 142, 177, + 178, 179, 45, 93, 46, 101, 123, 102, 124, 161, + 180, 162, 49, 50, 51, 128, 136, 52, 141, 137, + 138, 173, 16, 17, 131, 132, 111, 108, 19, 74, + 76, 75, 112, 113, 114, 53, 82, 83, 233, 259, + 272, 273, 54, 55, 234, 252, 267, 274, 268, 269, + 270, 84 +}; + + /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_int16 yytable[] = +{ + 18, 134, 47, 48, 6, 158, 56, 87, 88, 129, + 129, 171, 130, 130, 145, 107, 171, 109, 96, 57, + 187, 118, 119, 71, 56, 56, 72, 7, 8, 9, + 10, 11, 12, 125, -65, 169, 7, 8, 126, 7, + 8, 126, 168, 169, 196, 170, 146, 197, 89, 79, + 194, 129, 195, 219, 130, 90, 106, 207, 91, 135, + 208, 56, 172, 73, 174, 105, -175, 217, 80, 81, + 96, 110, 13, 94, 175, 14, 176, 115, 115, 115, + 15, 246, -175, 92, 129, 199, 129, 130, 167, 130, + 77, 215, 236, 133, 129, 139, 230, 130, 245, 85, + 235, 1, 2, 3, 216, 95, 241, 7, 8, 9, + 10, 11, 12, 129, 165, 78, 130, 115, 198, 166, + 106, 163, 164, 157, 56, 7, 8, 9, 10, 86, + 12, 255, 256, 257, 129, 116, 117, 130, 58, 21, + 59, 60, 61, 62, 22, 181, 184, 121, 147, 188, + 148, 122, 13, 140, 144, 14, 7, 8, 126, 127, + 15, 7, 8, 126, 146, 150, 153, 151, 154, 185, + 13, 63, 189, 14, 155, 182, 203, 192, 15, 139, + 202, 211, 193, 201, 206, 214, 221, 224, 247, 271, + 248, 249, 250, 251, 254, 213, 278, 275, 276, 277, + 281, 237, 69, 99, 258, 225, 159, 220, 160, 223, + 200, 231, 232, 103, 218, 56, 149, 152, 280, 244, + 70, 242, 243, 0, 0, 56, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 253, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 265, 266, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 21, 0, + 0, 0, 279, 22, 260, 261, 262, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 0, 226, 0, + 263, 0, 21, 0, 0, 0, 264, 22, 0, 0, + 0, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 0, 226, 0, 227, 0, 21, 0, 0, 0, + 228, 22, 0, 0, 0, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 0, 20, 0, 238, 0, + 21, 0, 0, 0, 239, 22, 0, 0, 0, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 100, + 0, 0, 0, 21, 0, 0, 0, 0, 22, 0, + 0, 0, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42 +}; + +static const yytype_int16 yycheck[] = +{ + 2, 91, 3, 3, 1, 121, 3, 47, 48, 90, + 91, 4, 90, 91, 13, 71, 4, 73, 7, 0, + 146, 77, 78, 46, 21, 22, 53, 3, 4, 5, + 6, 7, 8, 89, 7, 47, 3, 4, 5, 3, + 4, 5, 132, 47, 169, 57, 45, 172, 44, 7, + 165, 132, 167, 57, 132, 51, 3, 46, 54, 6, + 186, 58, 55, 46, 44, 62, 51, 55, 21, 22, + 7, 47, 48, 51, 54, 51, 56, 74, 75, 76, + 56, 50, 51, 56, 165, 175, 167, 165, 55, 167, + 46, 55, 217, 90, 175, 92, 212, 175, 224, 53, + 215, 40, 41, 42, 194, 58, 222, 3, 4, 5, + 6, 7, 8, 194, 47, 46, 194, 114, 174, 52, + 3, 123, 124, 120, 121, 3, 4, 5, 6, 53, + 8, 241, 242, 243, 215, 75, 76, 215, 10, 11, + 12, 13, 14, 15, 16, 142, 143, 51, 7, 151, + 9, 9, 48, 7, 44, 51, 3, 4, 5, 6, + 56, 3, 4, 5, 45, 49, 52, 47, 57, 7, + 48, 43, 7, 51, 50, 50, 7, 48, 56, 176, + 47, 47, 52, 52, 48, 48, 47, 47, 43, 7, + 230, 231, 232, 233, 43, 192, 47, 50, 43, 43, + 50, 221, 5, 60, 244, 211, 121, 202, 121, 206, + 176, 212, 212, 61, 196, 212, 108, 114, 278, 222, + 5, 222, 222, -1, -1, 222, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 234, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 252, 252, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 7, -1, -1, -1, 11, -1, + -1, -1, 274, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, -1, 7, -1, + 43, -1, 11, -1, -1, -1, 49, 16, -1, -1, + -1, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, -1, 7, -1, 43, -1, 11, -1, -1, -1, + 49, 16, -1, -1, -1, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, -1, 7, -1, 43, -1, + 11, -1, -1, -1, 49, 16, -1, -1, -1, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 7, + -1, -1, -1, 11, -1, -1, -1, -1, 16, -1, + -1, -1, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 40, 41, 42, 59, 60, 139, 3, 4, 5, + 6, 7, 8, 48, 51, 56, 110, 111, 114, 116, + 7, 11, 16, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 78, 85, 90, 92, 93, 95, 100, + 101, 102, 105, 123, 130, 131, 139, 0, 10, 12, + 13, 14, 15, 43, 63, 67, 68, 70, 71, 78, + 131, 46, 53, 46, 117, 119, 118, 46, 46, 7, + 130, 130, 124, 125, 139, 53, 53, 125, 125, 44, + 51, 54, 56, 91, 51, 130, 7, 61, 62, 62, + 7, 93, 95, 98, 69, 139, 3, 108, 115, 108, + 47, 114, 120, 121, 122, 139, 120, 120, 108, 108, + 79, 51, 9, 94, 96, 108, 5, 6, 103, 110, + 111, 112, 113, 139, 112, 6, 104, 107, 108, 139, + 7, 106, 86, 72, 44, 13, 45, 7, 9, 116, + 49, 47, 121, 52, 57, 50, 81, 139, 85, 90, + 92, 97, 99, 114, 114, 47, 52, 55, 112, 47, + 57, 4, 55, 109, 44, 54, 56, 87, 88, 89, + 98, 139, 50, 74, 139, 7, 64, 61, 114, 7, + 80, 82, 48, 52, 113, 113, 107, 107, 108, 112, + 104, 52, 47, 7, 73, 75, 48, 46, 61, 65, + 66, 47, 83, 139, 48, 55, 112, 55, 109, 57, + 89, 47, 76, 139, 47, 80, 7, 43, 49, 84, + 85, 93, 95, 126, 132, 113, 107, 73, 43, 49, + 77, 85, 93, 95, 126, 61, 50, 43, 125, 125, + 125, 125, 133, 139, 43, 124, 124, 124, 125, 127, + 17, 18, 19, 43, 49, 93, 95, 134, 136, 137, + 138, 7, 128, 129, 135, 50, 43, 43, 47, 114, + 128, 50 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ static const yytype_uint8 yyr1[] = { 0, 58, 59, 59, 59, 60, 60, 60, 60, 60, @@ -740,7 +926,7 @@ static const yytype_uint8 yyr1[] = 139 }; -/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ static const yytype_uint8 yyr2[] = { 0, 2, 2, 2, 2, 1, 2, 2, 2, 2, @@ -765,340 +951,41 @@ static const yytype_uint8 yyr2[] = 0 }; -/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state - STATE-NUM when YYTABLE doesn't specify something else to do. Zero - means the default is an error. */ -static const yytype_uint8 yydefact[] = -{ - 0, 190, 0, 190, 0, 2, 5, 116, 115, 127, - 128, 129, 0, 130, 134, 132, 125, 126, 3, 122, - 88, 190, 190, 146, 147, 148, 149, 150, 151, 152, - 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, - 163, 164, 165, 89, 190, 68, 71, 190, 190, 4, - 87, 105, 67, 83, 0, 90, 174, 1, 190, 0, - 0, 0, 190, 6, 9, 10, 11, 7, 28, 29, - 8, 0, 0, 123, 190, 190, 190, 0, 0, 175, - 44, 0, 168, 80, 166, 69, 72, 82, 81, 0, - 190, 0, 190, 0, 58, 30, 12, 14, 0, 16, - 88, 76, 77, 23, 24, 25, 113, 138, 139, 0, - 143, 144, 0, 140, 190, 142, 0, 0, 136, 137, - 190, 190, 167, 0, 0, 85, 119, 120, 0, 118, - 117, 121, 92, 91, 86, 111, 0, 99, 112, 98, - 107, 66, 190, 190, 0, 17, 0, 26, 27, 124, - 131, 143, 141, 135, 133, 0, 0, 47, 79, 74, - 75, 78, 0, 70, 73, 0, 84, 0, 94, 0, - 106, 114, 0, 101, 0, 0, 190, 0, 61, 62, - 64, 60, 0, 0, 33, 13, 0, 15, 145, 46, - 49, 48, 190, 0, 95, 93, 102, 100, 108, 109, - 0, 59, 0, 32, 35, 34, 190, 20, 21, 18, - 19, 0, 190, 51, 176, 0, 97, 0, 104, 110, - 63, 0, 190, 37, 0, 50, 88, 52, 45, 0, - 190, 190, 190, 190, 190, 96, 103, 36, 38, 31, - 0, 190, 190, 190, 190, 22, 169, 53, 54, 57, - 56, 55, 0, 178, 39, 40, 43, 42, 41, 0, - 184, 0, 187, 179, 177, 189, 188, 180, 181, 0, - 0, 171, 172, 170, 0, 186, 182, 183, 0, 0, - 173, 185 -}; -/* YYDEFGOTO[NTERM-NUM]. */ -static const yytype_int16 yydefgoto[] = -{ - -1, 4, 5, 97, 98, 64, 186, 209, 210, 65, - 66, 104, 67, 68, 143, 204, 183, 205, 222, 240, - 43, 120, 190, 156, 191, 212, 229, 44, 142, 177, - 178, 179, 45, 93, 46, 101, 123, 102, 124, 161, - 180, 162, 49, 50, 51, 128, 136, 52, 141, 137, - 138, 173, 16, 17, 131, 132, 111, 109, 19, 74, - 76, 75, 112, 113, 114, 53, 82, 83, 233, 259, - 272, 273, 54, 55, 234, 252, 267, 274, 268, 269, - 270, 84 -}; +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 -/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing - STATE-NUM. */ -#define YYPACT_NINF -127 -static const yytype_int16 yypact[] = -{ - 8, -127, 104, 359, 35, 128, -127, -127, -127, 20, - -127, 22, 28, -127, -127, -127, 37, 53, -127, -127, - 54, 139, 139, -127, -127, -127, -127, -127, -127, -127, - -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, - -127, -127, -127, -127, -127, 95, 97, -127, -127, -127, - 2, -127, 26, -127, 100, -127, -127, -127, 139, 145, - 145, 391, -127, -127, -127, -127, -127, -127, -127, -127, - -127, 151, 151, -127, 24, 24, 24, 151, 151, -127, - -127, 108, 152, -127, -127, -127, -127, -127, -127, 151, - 127, 98, 64, 154, -127, -127, -127, 119, 13, 120, - -127, -127, -127, -127, 106, -127, -127, -127, -127, 122, - -127, -127, 115, -127, 121, -127, 114, 110, -127, -127, - 124, 359, -127, 104, 104, -127, -127, -127, 12, -127, - -127, -127, 14, -127, -127, -127, 16, 7, -127, -127, - -127, 1, 391, 125, 161, -127, 145, -127, -127, -127, - -127, 104, -127, -127, -127, 164, 129, -127, -127, -127, - -127, -127, 130, -127, -127, 127, -127, 127, -127, 64, - -127, -127, 64, -127, 151, 98, 64, 131, 133, -127, - -127, -127, 168, 136, -127, -127, 6, 119, -127, -127, - -127, 134, -127, 137, 33, -127, 19, -127, -127, -127, - 43, -127, 391, -127, -127, 140, -127, -127, 119, -127, - 141, 164, 291, -127, -127, 127, -127, 64, -127, -127, - -127, 168, 325, -127, 145, -127, 69, -127, -127, 143, - -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, - 146, -127, -127, -127, -127, 119, -127, -127, -127, -127, - -127, -127, 257, -127, -127, 152, 152, 152, -127, 186, - -127, 147, -127, -127, -127, -127, -127, -127, -127, 153, - 155, -127, -127, 156, 104, -127, -127, -127, 186, 149, - -127, -127 -}; +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab -/* YYPGOTO[NTERM-NUM]. */ -static const yytype_int16 yypgoto[] = -{ - -127, -127, -127, -126, 142, -127, -127, -127, -127, -127, - -127, -127, -127, -127, -127, -21, -127, -127, -127, -127, - 196, -127, -6, -127, -127, -127, -127, -116, -127, -127, - -127, 4, 86, -127, 87, -1, -127, 0, -127, -127, - 157, -127, -127, -127, -127, -127, 34, -127, -127, -125, - -56, 17, -81, -78, -90, -124, -2, -127, 105, -127, - -127, -127, 60, 102, -127, -127, -85, -40, -5, -127, - -59, -127, 18, 215, -127, -127, -127, -127, -127, -127, - -127, 3 -}; - -/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If - positive, shift that token. If negative, reduce the rule which - number is the opposite. If zero, do what YYDEFACT says. - If YYTABLE_NINF, syntax error. */ -#define YYTABLE_NINF -176 -static const yytype_int16 yytable[] = -{ - 18, 134, 47, 48, 6, 158, 56, 87, 88, 129, - 129, 171, 130, 130, 96, 107, 108, 7, 8, 126, - 187, 118, 119, 171, 56, 56, 145, 7, 8, 9, - 10, 11, 12, 125, -65, 57, 7, 8, 126, 80, - 81, 194, 168, 195, 196, 174, 89, 197, 1, 2, - 3, 129, 207, 90, 130, 175, 91, 176, 146, 165, - 208, 56, 172, 169, 166, 105, 71, 106, 72, 167, - 135, 110, 13, 170, 217, 14, 95, 115, 115, 115, - 15, 73, 92, 77, 129, 199, 129, 130, 215, 130, - 169, 235, 236, 133, 129, 139, 230, 130, 245, 78, - 219, 7, 8, 126, 216, -175, 241, 7, 8, 9, - 10, 11, 12, 129, 147, 148, 130, 115, 198, 246, - -175, 163, 164, 157, 56, 7, 8, 9, 10, 11, - 7, 8, 126, 127, 129, 116, 117, 130, 58, 21, - 59, 60, 61, 62, 22, 181, 184, 79, 85, 188, - 86, 94, 13, 96, 106, 14, 255, 256, 257, 121, - 15, 122, 140, 144, 150, 146, 153, 154, 151, 185, - 13, 63, 189, 14, 155, 182, 203, 192, 15, 139, - 202, 211, 193, 201, 206, 214, 247, 221, 224, 254, - 248, 249, 250, 251, 271, 213, 276, 275, 277, 281, - 237, 69, 99, 278, 258, 225, 220, 159, 160, 223, - 200, 231, 232, 218, 149, 56, 152, 244, 103, 280, - 70, 242, 243, 0, 0, 56, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 253, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 265, 266, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 100, 0, 0, 21, 0, - 0, 0, 279, 22, 260, 261, 262, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 0, 0, 226, - 263, 0, 21, 0, 0, 0, 264, 22, 0, 0, - 0, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 0, 0, 226, 227, 0, 21, 0, 0, 0, - 228, 22, 0, 0, 0, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - 38, 39, 40, 41, 42, 0, 0, 20, 238, 0, - 21, 0, 0, 0, 239, 22, 0, 0, 0, 23, - 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 38, 39, 40, 41, 42, 100, - 0, 0, 21, 0, 0, 0, 0, 22, 0, 0, - 0, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42 -}; - -static const yytype_int16 yycheck[] = -{ - 2, 91, 3, 3, 1, 121, 3, 47, 48, 90, - 91, 4, 90, 91, 8, 71, 72, 3, 4, 5, - 146, 77, 78, 4, 21, 22, 13, 3, 4, 5, - 6, 7, 8, 89, 8, 0, 3, 4, 5, 21, - 22, 165, 132, 167, 169, 44, 44, 172, 40, 41, - 42, 132, 46, 51, 132, 54, 54, 56, 45, 47, - 186, 58, 55, 47, 52, 62, 46, 3, 46, 55, - 6, 47, 48, 57, 55, 51, 58, 74, 75, 76, - 56, 53, 56, 46, 165, 175, 167, 165, 55, 167, - 47, 215, 217, 90, 175, 92, 212, 175, 224, 46, - 57, 3, 4, 5, 194, 51, 222, 3, 4, 5, - 6, 7, 8, 194, 8, 9, 194, 114, 174, 50, - 51, 123, 124, 120, 121, 3, 4, 5, 6, 7, - 3, 4, 5, 6, 215, 75, 76, 215, 10, 11, - 12, 13, 14, 15, 16, 142, 143, 8, 53, 151, - 53, 51, 48, 8, 3, 51, 241, 242, 243, 51, - 56, 9, 8, 44, 49, 45, 52, 57, 47, 8, - 48, 43, 8, 51, 50, 50, 8, 48, 56, 176, - 47, 47, 52, 52, 48, 48, 43, 47, 47, 43, - 230, 231, 232, 233, 8, 192, 43, 50, 43, 50, - 221, 5, 60, 47, 244, 211, 202, 121, 121, 206, - 176, 212, 212, 196, 109, 212, 114, 222, 61, 278, - 5, 222, 222, -1, -1, 222, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 234, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 252, 252, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 8, -1, -1, 11, -1, - -1, -1, 274, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39, -1, -1, 8, - 43, -1, 11, -1, -1, -1, 49, 16, -1, -1, - -1, 20, 21, 22, 23, 24, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, - 39, -1, -1, 8, 43, -1, 11, -1, -1, -1, - 49, 16, -1, -1, -1, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, - 35, 36, 37, 38, 39, -1, -1, 8, 43, -1, - 11, -1, -1, -1, 49, 16, -1, -1, -1, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, 39, 8, - -1, -1, 11, -1, -1, -1, -1, 16, -1, -1, - -1, 20, 21, 22, 23, 24, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, - 39 -}; - -/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing - symbol of state STATE-NUM. */ -static const yytype_uint8 yystos[] = -{ - 0, 40, 41, 42, 59, 60, 139, 3, 4, 5, - 6, 7, 8, 48, 51, 56, 110, 111, 114, 116, - 8, 11, 16, 20, 21, 22, 23, 24, 25, 26, - 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, - 37, 38, 39, 78, 85, 90, 92, 93, 95, 100, - 101, 102, 105, 123, 130, 131, 139, 0, 10, 12, - 13, 14, 15, 43, 63, 67, 68, 70, 71, 78, - 131, 46, 46, 53, 117, 119, 118, 46, 46, 8, - 130, 130, 124, 125, 139, 53, 53, 125, 125, 44, - 51, 54, 56, 91, 51, 130, 8, 61, 62, 62, - 8, 93, 95, 98, 69, 139, 3, 108, 108, 115, - 47, 114, 120, 121, 122, 139, 120, 120, 108, 108, - 79, 51, 9, 94, 96, 108, 5, 6, 103, 110, - 111, 112, 113, 139, 112, 6, 104, 107, 108, 139, - 8, 106, 86, 72, 44, 13, 45, 8, 9, 116, - 49, 47, 121, 52, 57, 50, 81, 139, 85, 90, - 92, 97, 99, 114, 114, 47, 52, 55, 112, 47, - 57, 4, 55, 109, 44, 54, 56, 87, 88, 89, - 98, 139, 50, 74, 139, 8, 64, 61, 114, 8, - 80, 82, 48, 52, 113, 113, 107, 107, 108, 112, - 104, 52, 47, 8, 73, 75, 48, 46, 61, 65, - 66, 47, 83, 139, 48, 55, 112, 55, 109, 57, - 89, 47, 76, 139, 47, 80, 8, 43, 49, 84, - 85, 93, 95, 126, 132, 113, 107, 73, 43, 49, - 77, 85, 93, 95, 126, 61, 50, 43, 125, 125, - 125, 125, 133, 139, 43, 124, 124, 124, 125, 127, - 17, 18, 19, 43, 49, 93, 95, 134, 136, 137, - 138, 8, 128, 129, 135, 50, 43, 43, 47, 114, - 128, 50 -}; - -#define yyerrok (yyerrstatus = 0) -#define yyclearin (yychar = YYEMPTY) -#define YYEMPTY (-2) -#define YYEOF 0 - -#define YYACCEPT goto yyacceptlab -#define YYABORT goto yyabortlab -#define YYERROR goto yyerrorlab - - -/* Like YYERROR except do call yyerror. This remains here temporarily - to ease the transition to the new meaning of YYERROR, for GCC. - Once GCC version 2 has supplanted version 1, this can go. However, - YYFAIL appears to be in use. Nevertheless, it is formally deprecated - in Bison 2.4.2's NEWS entry, where a plan to phase it out is - discussed. */ - -#define YYFAIL goto yyerrlab -#if defined YYFAIL - /* This is here to suppress warnings from the GCC cpp's - -Wunused-macros. Normally we don't worry about that warning, but - some users do, and we want to make it easy for users to remove - YYFAIL uses, which will produce warnings from Bison 2.5. */ -#endif #define YYRECOVERING() (!!yyerrstatus) -#define YYBACKUP(Token, Value) \ -do \ - if (yychar == YYEMPTY && yylen == 1) \ - { \ - yychar = (Token); \ - yylval = (Value); \ - yytoken = YYTRANSLATE (yychar); \ - YYPOPSTACK (1); \ - goto yybackup; \ - } \ - else \ - { \ +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ yyerror (YY_("syntax error: cannot back up")); \ - YYERROR; \ - } \ -while (YYID (0)) + YYERROR; \ + } \ +while (0) + +/* Error token number */ +#define YYTERROR 1 +#define YYERRCODE 256 -#define YYTERROR 1 -#define YYERRCODE 256 - - -/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. - If N is 0, then set CURRENT to the empty location which ends - the previous symbol: RHS[0] (always defined). */ - -#define YYRHSLOC(Rhs, K) ((Rhs)[K]) -#ifndef YYLLOC_DEFAULT -# define YYLLOC_DEFAULT(Current, Rhs, N) \ - do \ - if (YYID (N)) \ - { \ - (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ - (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ - (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ - (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ - } \ - else \ - { \ - (Current).first_line = (Current).last_line = \ - YYRHSLOC (Rhs, 0).last_line; \ - (Current).first_column = (Current).last_column = \ - YYRHSLOC (Rhs, 0).last_column; \ - } \ - while (YYID (0)) -#endif - - -/* YY_LOCATION_PRINT -- Print the location on the stream. - This macro was not mandated originally: define only if we know - we won't break user code: when these are the locations we know. */ - -#ifndef YY_LOCATION_PRINT -# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL -# define YY_LOCATION_PRINT(File, Loc) \ - fprintf (File, "%d.%d-%d.%d", \ - (Loc).first_line, (Loc).first_column, \ - (Loc).last_line, (Loc).last_column) -# else -# define YY_LOCATION_PRINT(File, Loc) ((void) 0) -# endif -#endif - - -/* YYLEX -- calling `yylex' with the right arguments. */ - -#ifdef YYLEX_PARAM -# define YYLEX yylex (YYLEX_PARAM) -#else -# define YYLEX yylex () -#endif /* Enable debugging if requested. */ #if YYDEBUG @@ -1108,54 +995,46 @@ while (YYID (0)) # define YYFPRINTF fprintf # endif -# define YYDPRINTF(Args) \ -do { \ - if (yydebug) \ - YYFPRINTF Args; \ -} while (YYID (0)) +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) -# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ -do { \ - if (yydebug) \ - { \ - YYFPRINTF (stderr, "%s ", Title); \ - yy_symbol_print (stderr, \ - Type, Value); \ - YYFPRINTF (stderr, "\n"); \ - } \ -} while (YYID (0)) +/* This macro is provided for backward compatibility. */ +#ifndef YY_LOCATION_PRINT +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +#endif -/*--------------------------------. -| Print this symbol on YYOUTPUT. | -`--------------------------------*/ +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*----------------------------------------. +| Print this symbol's value on YYOUTPUT. | +`----------------------------------------*/ -/*ARGSUSED*/ -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) static void yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) -#else -static void -yy_symbol_value_print (yyoutput, yytype, yyvaluep) - FILE *yyoutput; - int yytype; - YYSTYPE const * const yyvaluep; -#endif { + FILE *yyo = yyoutput; + YYUSE (yyo); if (!yyvaluep) return; # ifdef YYPRINT if (yytype < YYNTOKENS) YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); -# else - YYUSE (yyoutput); # endif - switch (yytype) - { - default: - break; - } + YYUSE (yytype); } @@ -1163,22 +1042,11 @@ yy_symbol_value_print (yyoutput, yytype, yyvaluep) | Print this symbol on YYOUTPUT. | `--------------------------------*/ -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) static void yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) -#else -static void -yy_symbol_print (yyoutput, yytype, yyvaluep) - FILE *yyoutput; - int yytype; - YYSTYPE const * const yyvaluep; -#endif { - if (yytype < YYNTOKENS) - YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); - else - YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + YYFPRINTF (yyoutput, "%s %s (", + yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); yy_symbol_value_print (yyoutput, yytype, yyvaluep); YYFPRINTF (yyoutput, ")"); @@ -1189,16 +1057,8 @@ yy_symbol_print (yyoutput, yytype, yyvaluep) | TOP (included). | `------------------------------------------------------------------*/ -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) static void yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) -#else -static void -yy_stack_print (yybottom, yytop) - yytype_int16 *yybottom; - yytype_int16 *yytop; -#endif { YYFPRINTF (stderr, "Stack now"); for (; yybottom <= yytop; yybottom++) @@ -1209,49 +1069,42 @@ yy_stack_print (yybottom, yytop) YYFPRINTF (stderr, "\n"); } -# define YY_STACK_PRINT(Bottom, Top) \ -do { \ - if (yydebug) \ - yy_stack_print ((Bottom), (Top)); \ -} while (YYID (0)) +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) static void -yy_reduce_print (YYSTYPE *yyvsp, int yyrule) -#else -static void -yy_reduce_print (yyvsp, yyrule) - YYSTYPE *yyvsp; - int yyrule; -#endif +yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule) { + unsigned long int yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; - unsigned long int yylno = yyrline[yyrule]; YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", - yyrule - 1, yylno); + yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { YYFPRINTF (stderr, " $%d = ", yyi + 1); - yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], - &(yyvsp[(yyi + 1) - (yynrhs)]) - ); + yy_symbol_print (stderr, + yystos[yyssp[yyi + 1 - yynrhs]], + &(yyvsp[(yyi + 1) - (yynrhs)]) + ); YYFPRINTF (stderr, "\n"); } } -# define YY_REDUCE_PRINT(Rule) \ -do { \ - if (yydebug) \ - yy_reduce_print (yyvsp, Rule); \ -} while (YYID (0)) +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule); \ +} while (0) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ @@ -1265,7 +1118,7 @@ int yydebug; /* YYINITDEPTH -- initial size of the parser's stacks. */ -#ifndef YYINITDEPTH +#ifndef YYINITDEPTH # define YYINITDEPTH 200 #endif @@ -1280,7 +1133,6 @@ int yydebug; # define YYMAXDEPTH 10000 #endif - #if YYERROR_VERBOSE @@ -1289,15 +1141,8 @@ int yydebug; # define yystrlen strlen # else /* Return the length of YYSTR. */ -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) static YYSIZE_T yystrlen (const char *yystr) -#else -static YYSIZE_T -yystrlen (yystr) - const char *yystr; -#endif { YYSIZE_T yylen; for (yylen = 0; yystr[yylen]; yylen++) @@ -1313,16 +1158,8 @@ yystrlen (yystr) # else /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in YYDEST. */ -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) static char * yystpcpy (char *yydest, const char *yysrc) -#else -static char * -yystpcpy (yydest, yysrc) - char *yydest; - const char *yysrc; -#endif { char *yyd = yydest; const char *yys = yysrc; @@ -1352,27 +1189,27 @@ yytnamerr (char *yyres, const char *yystr) char const *yyp = yystr; for (;;) - switch (*++yyp) - { - case '\'': - case ',': - goto do_not_strip_quotes; + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; - case '\\': - if (*++yyp != '\\') - goto do_not_strip_quotes; - /* Fall through. */ - default: - if (yyres) - yyres[yyn] = *yyp; - yyn++; - break; + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; - case '"': - if (yyres) - yyres[yyn] = '\0'; - return yyn; - } + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } do_not_strip_quotes: ; } @@ -1383,161 +1220,160 @@ yytnamerr (char *yyres, const char *yystr) } # endif -/* Copy into YYRESULT an error message about the unexpected token - YYCHAR while in state YYSTATE. Return the number of bytes copied, - including the terminating null byte. If YYRESULT is null, do not - copy anything; just return the number of bytes that would be - copied. As a special case, return 0 if an ordinary "syntax error" - message will do. Return YYSIZE_MAXIMUM if overflow occurs during - size calculation. */ -static YYSIZE_T -yysyntax_error (char *yyresult, int yystate, int yychar) +/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message + about the unexpected token YYTOKEN for the state stack whose top is + YYSSP. + + Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is + not large enough to hold the message. In that case, also set + *YYMSG_ALLOC to the required number of bytes. Return 2 if the + required number of bytes is too large to store. */ +static int +yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, + yytype_int16 *yyssp, int yytoken) { - int yyn = yypact[yystate]; + YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); + YYSIZE_T yysize = yysize0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + /* Internationalized format string. */ + const char *yyformat = YY_NULLPTR; + /* Arguments of yyformat. */ + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + /* Number of reported tokens (one for the "unexpected", one per + "expected"). */ + int yycount = 0; - if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) - return 0; - else + /* There are many possibilities here to consider: + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yychar) is if + this state is a consistent state with a default action. Thus, + detecting the absence of a lookahead is sufficient to determine + that there is no unexpected or expected token to report. In that + case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is a + consistent state with a default action. There might have been a + previous inconsistent state, consistent state with a non-default + action, or user semantic action that manipulated yychar. + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state merging + (from LALR or IELR) and default reductions corrupt the expected + token list. However, the list is correct for canonical LR with + one exception: it will still contain any token that will not be + accepted due to an error action in a later state. + */ + if (yytoken != YYEMPTY) { - int yytype = YYTRANSLATE (yychar); - YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); - YYSIZE_T yysize = yysize0; - YYSIZE_T yysize1; - int yysize_overflow = 0; - enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; - char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; - int yyx; + int yyn = yypact[*yyssp]; + yyarg[yycount++] = yytname[yytoken]; + if (!yypact_value_is_default (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yyx; -# if 0 - /* This is so xgettext sees the translatable formats that are - constructed on the fly. */ - YY_("syntax error, unexpected %s"); - YY_("syntax error, unexpected %s, expecting %s"); - YY_("syntax error, unexpected %s, expecting %s or %s"); - YY_("syntax error, unexpected %s, expecting %s or %s or %s"); - YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); -# endif - char *yyfmt; - char const *yyf; - static char const yyunexpected[] = "syntax error, unexpected %s"; - static char const yyexpecting[] = ", expecting %s"; - static char const yyor[] = " or %s"; - char yyformat[sizeof yyunexpected - + sizeof yyexpecting - 1 - + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) - * (sizeof yyor - 1))]; - char const *yyprefix = yyexpecting; - - /* Start YYX at -YYN if negative to avoid negative indexes in - YYCHECK. */ - int yyxbegin = yyn < 0 ? -yyn : 0; - - /* Stay within bounds of both yycheck and yytname. */ - int yychecklim = YYLAST - yyn + 1; - int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; - int yycount = 1; - - yyarg[0] = yytname[yytype]; - yyfmt = yystpcpy (yyformat, yyunexpected); - - for (yyx = yyxbegin; yyx < yyxend; ++yyx) - if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) - { - if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) - { - yycount = 1; - yysize = yysize0; - yyformat[sizeof yyunexpected - 1] = '\0'; - break; - } - yyarg[yycount++] = yytname[yyx]; - yysize1 = yysize + yytnamerr (0, yytname[yyx]); - yysize_overflow |= (yysize1 < yysize); - yysize = yysize1; - yyfmt = yystpcpy (yyfmt, yyprefix); - yyprefix = yyor; - } - - yyf = YY_(yyformat); - yysize1 = yysize + yystrlen (yyf); - yysize_overflow |= (yysize1 < yysize); - yysize = yysize1; - - if (yysize_overflow) - return YYSIZE_MAXIMUM; - - if (yyresult) - { - /* Avoid sprintf, as that infringes on the user's name space. - Don't have undefined behavior even if the translation - produced a string with the wrong number of "%s"s. */ - char *yyp = yyresult; - int yyi = 0; - while ((*yyp = *yyf) != '\0') - { - if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) - { - yyp += yytnamerr (yyp, yyarg[yyi++]); - yyf += 2; - } - else - { - yyp++; - yyf++; - } - } - } - return yysize; + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR + && !yytable_value_is_error (yytable[yyx + yyn])) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + break; + } + yyarg[yycount++] = yytname[yyx]; + { + YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); + if (! (yysize <= yysize1 + && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + } + } } + + switch (yycount) + { +# define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +# undef YYCASE_ + } + + { + YYSIZE_T yysize1 = yysize + yystrlen (yyformat); + if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + + if (*yymsg_alloc < yysize) + { + *yymsg_alloc = 2 * yysize; + if (! (yysize <= *yymsg_alloc + && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) + *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; + return 1; + } + + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + { + char *yyp = *yymsg; + int yyi = 0; + while ((*yyp = *yyformat) != '\0') + if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyformat += 2; + } + else + { + yyp++; + yyformat++; + } + } + return 0; } #endif /* YYERROR_VERBOSE */ - /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ -/*ARGSUSED*/ -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) static void yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) -#else -static void -yydestruct (yymsg, yytype, yyvaluep) - const char *yymsg; - int yytype; - YYSTYPE *yyvaluep; -#endif { YYUSE (yyvaluep); - if (!yymsg) yymsg = "Deleting"; YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); - switch (yytype) - { - - default: - break; - } + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YYUSE (yytype); + YY_IGNORE_MAYBE_UNINITIALIZED_END } -/* Prevent warnings from -Wmissing-prototypes. */ -#ifdef YYPARSE_PARAM -#if defined __STDC__ || defined __cplusplus -int yyparse (void *YYPARSE_PARAM); -#else -int yyparse (); -#endif -#else /* ! YYPARSE_PARAM */ -#if defined __STDC__ || defined __cplusplus -int yyparse (void); -#else -int yyparse (); -#endif -#endif /* ! YYPARSE_PARAM */ + /* The lookahead symbol. */ @@ -1545,49 +1381,26 @@ int yychar; /* The semantic value of the lookahead symbol. */ YYSTYPE yylval; - /* Number of syntax errors so far. */ int yynerrs; +/*----------. +| yyparse. | +`----------*/ -/*-------------------------. -| yyparse or yypush_parse. | -`-------------------------*/ - -#ifdef YYPARSE_PARAM -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) -int -yyparse (void *YYPARSE_PARAM) -#else -int -yyparse (YYPARSE_PARAM) - void *YYPARSE_PARAM; -#endif -#else /* ! YYPARSE_PARAM */ -#if (defined __STDC__ || defined __C99__FUNC__ \ - || defined __cplusplus || defined _MSC_VER) int yyparse (void) -#else -int -yyparse () - -#endif -#endif { - - int yystate; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus; /* The stacks and their tools: - `yyss': related to states. - `yyvs': related to semantic values. + 'yyss': related to states. + 'yyvs': related to semantic values. - Refer to the stacks thru separate pointers, to allow yyoverflow + Refer to the stacks through separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* The state stack. */ @@ -1605,7 +1418,7 @@ yyparse () int yyn; int yyresult; /* Lookahead token as an internal (translated) token number. */ - int yytoken; + int yytoken = 0; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; @@ -1623,9 +1436,8 @@ yyparse () Keep to zero when no symbol should be popped. */ int yylen = 0; - yytoken = 0; - yyss = yyssa; - yyvs = yyvsa; + yyssp = yyss = yyssa; + yyvsp = yyvs = yyvsa; yystacksize = YYINITDEPTH; YYDPRINTF ((stderr, "Starting parse\n")); @@ -1634,14 +1446,6 @@ yyparse () yyerrstatus = 0; yynerrs = 0; yychar = YYEMPTY; /* Cause a token to be read. */ - - /* Initialize stack pointers. - Waste one element of value and location stack - so that they stay on the same level as the state stack. - The wasted elements are never initialized. */ - yyssp = yyss; - yyvsp = yyvs; - goto yysetstate; /*------------------------------------------------------------. @@ -1662,23 +1466,23 @@ yyparse () #ifdef yyoverflow { - /* Give user a chance to reallocate the stack. Use copies of - these so that the &'s don't force the real ones into - memory. */ - YYSTYPE *yyvs1 = yyvs; - yytype_int16 *yyss1 = yyss; + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; - /* Each stack pointer address is followed by the size of the - data in use in that stack, in bytes. This used to be a - conditional around just the two extra args, but that might - be undefined if yyoverflow is a macro. */ - yyoverflow (YY_("memory exhausted"), - &yyss1, yysize * sizeof (*yyssp), - &yyvs1, yysize * sizeof (*yyvsp), - &yystacksize); + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yystacksize); - yyss = yyss1; - yyvs = yyvs1; + yyss = yyss1; + yyvs = yyvs1; } #else /* no yyoverflow */ # ifndef YYSTACK_RELOCATE @@ -1686,22 +1490,22 @@ yyparse () # else /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) - goto yyexhaustedlab; + goto yyexhaustedlab; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) - yystacksize = YYMAXDEPTH; + yystacksize = YYMAXDEPTH; { - yytype_int16 *yyss1 = yyss; - union yyalloc *yyptr = - (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); - if (! yyptr) - goto yyexhaustedlab; - YYSTACK_RELOCATE (yyss_alloc, yyss); - YYSTACK_RELOCATE (yyvs_alloc, yyvs); + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); # undef YYSTACK_RELOCATE - if (yyss1 != yyssa) - YYSTACK_FREE (yyss1); + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); } # endif #endif /* no yyoverflow */ @@ -1710,10 +1514,10 @@ yyparse () yyvsp = yyvs + yysize - 1; YYDPRINTF ((stderr, "Stack size increased to %lu\n", - (unsigned long int) yystacksize)); + (unsigned long int) yystacksize)); if (yyss + yystacksize - 1 <= yyssp) - YYABORT; + YYABORT; } YYDPRINTF ((stderr, "Entering state %d\n", yystate)); @@ -1733,7 +1537,7 @@ yybackup: /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; - if (yyn == YYPACT_NINF) + if (yypact_value_is_default (yyn)) goto yydefault; /* Not known => get a lookahead token if don't already have one. */ @@ -1742,7 +1546,7 @@ yybackup: if (yychar == YYEMPTY) { YYDPRINTF ((stderr, "Reading a token: ")); - yychar = YYLEX; + yychar = yylex (); } if (yychar <= YYEOF) @@ -1764,8 +1568,8 @@ yybackup: yyn = yytable[yyn]; if (yyn <= 0) { - if (yyn == 0 || yyn == YYTABLE_NINF) - goto yyerrlab; + if (yytable_value_is_error (yyn)) + goto yyerrlab; yyn = -yyn; goto yyreduce; } @@ -1782,7 +1586,9 @@ yybackup: yychar = YYEMPTY; yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END goto yynewstate; @@ -1805,7 +1611,7 @@ yyreduce: yylen = yyr2[yyn]; /* If YYLEN is nonzero, implement the default value of the action: - `$$ = $1'. + '$$ = $1'. Otherwise, the following line sets YYVAL to garbage. This behavior is undocumented and Bison @@ -1819,112 +1625,100 @@ yyreduce: switch (yyn) { case 4: - -/* Line 1464 of yacc.c */ -#line 171 "dcParser.yxx" +#line 177 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - parameter_description = (yyvsp[(2) - (2)].u.field); + parameter_description = (yyvsp[0].u.field); } +#line 1633 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 7: - -/* Line 1464 of yacc.c */ -#line 180 "dcParser.yxx" +#line 186 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (!dc_file->add_class((yyvsp[(2) - (2)].u.dclass))) { - DCClass *old_class = dc_file->get_class_by_name((yyvsp[(2) - (2)].u.dclass)->get_name()); - if (old_class != (DCClass *)NULL && old_class->is_bogus_class()) { - yyerror("Base class defined after its first reference: " + (yyvsp[(2) - (2)].u.dclass)->get_name()); + if (!dc_file->add_class((yyvsp[0].u.dclass))) { + DCClass *old_class = dc_file->get_class_by_name((yyvsp[0].u.dclass)->get_name()); + if (old_class != nullptr && old_class->is_bogus_class()) { + yyerror("Base class defined after its first reference: " + (yyvsp[0].u.dclass)->get_name()); } else { - yyerror("Duplicate class name: " + (yyvsp[(2) - (2)].u.dclass)->get_name()); + yyerror("Duplicate class name: " + (yyvsp[0].u.dclass)->get_name()); } } } +#line 1648 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 8: - -/* Line 1464 of yacc.c */ -#line 191 "dcParser.yxx" +#line 197 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (!dc_file->add_switch((yyvsp[(2) - (2)].u.dswitch))) { - yyerror("Duplicate class name: " + (yyvsp[(2) - (2)].u.dswitch)->get_name()); + if (!dc_file->add_switch((yyvsp[0].u.dswitch))) { + yyerror("Duplicate class name: " + (yyvsp[0].u.dswitch)->get_name()); } } +#line 1658 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 13: - -/* Line 1464 of yacc.c */ -#line 204 "dcParser.yxx" +#line 210 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.str) = (yyvsp[(1) - (3)].str) + string("/") + (yyvsp[(3) - (3)].str); + (yyval.str) = (yyvsp[-2].str) + string("/") + (yyvsp[0].str); } +#line 1666 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 15: - -/* Line 1464 of yacc.c */ -#line 212 "dcParser.yxx" +#line 218 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.str) = (yyvsp[(1) - (3)].str) + string(".") + (yyvsp[(3) - (3)].str); + (yyval.str) = (yyvsp[-2].str) + string(".") + (yyvsp[0].str); } +#line 1674 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 16: - -/* Line 1464 of yacc.c */ -#line 219 "dcParser.yxx" +#line 225 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - dc_file->add_import_module((yyvsp[(2) - (2)].str)); + dc_file->add_import_module((yyvsp[0].str)); } +#line 1682 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 17: - -/* Line 1464 of yacc.c */ -#line 223 "dcParser.yxx" +#line 229 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - dc_file->add_import_module((yyvsp[(2) - (3)].str)); + dc_file->add_import_module((yyvsp[-1].str)); } +#line 1690 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 20: - -/* Line 1464 of yacc.c */ -#line 232 "dcParser.yxx" +#line 238 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { dc_file->add_import_symbol("*"); } +#line 1698 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 21: - -/* Line 1464 of yacc.c */ -#line 239 "dcParser.yxx" +#line 245 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - dc_file->add_import_symbol((yyvsp[(1) - (1)].str)); + dc_file->add_import_symbol((yyvsp[0].str)); } +#line 1706 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 22: - -/* Line 1464 of yacc.c */ -#line 243 "dcParser.yxx" +#line 249 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - dc_file->add_import_symbol((yyvsp[(3) - (3)].str)); + dc_file->add_import_symbol((yyvsp[0].str)); } +#line 1714 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 23: - -/* Line 1464 of yacc.c */ -#line 250 "dcParser.yxx" +#line 256 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(2) - (2)].u.parameter) != (DCParameter *)NULL) { - DCTypedef *dtypedef = new DCTypedef((yyvsp[(2) - (2)].u.parameter)); - + if ((yyvsp[0].u.parameter) != nullptr) { + DCTypedef *dtypedef = new DCTypedef((yyvsp[0].u.parameter)); + if (!dc_file->add_typedef(dtypedef)) { DCTypedef *old_typedef = dc_file->get_typedef_by_name(dtypedef->get_name()); if (old_typedef->is_bogus_typedef()) { @@ -1935,343 +1729,316 @@ yyreduce: } } } +#line 1733 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 26: - -/* Line 1464 of yacc.c */ -#line 273 "dcParser.yxx" +#line 279 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - dc_file->add_keyword((yyvsp[(2) - (2)].str)); + dc_file->add_keyword((yyvsp[0].str)); } +#line 1741 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 27: - -/* Line 1464 of yacc.c */ -#line 277 "dcParser.yxx" +#line 283 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { // This keyword has already been defined. But since we are now // explicitly defining it, clear its bitmask, so that we will have a // new hash code--doing this will allow us to phase out the // historical hash code support later. - ((DCKeyword *)(yyvsp[(2) - (2)].u.keyword))->clear_historical_flag(); + ((DCKeyword *)(yyvsp[0].u.keyword))->clear_historical_flag(); } +#line 1753 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 30: - -/* Line 1464 of yacc.c */ -#line 293 "dcParser.yxx" +#line 299 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_class = new DCClass(dc_file, (yyvsp[(2) - (2)].str), false, false); + current_class = new DCClass(dc_file, (yyvsp[0].str), false, false); } +#line 1761 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 31: - -/* Line 1464 of yacc.c */ -#line 297 "dcParser.yxx" +#line 303 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.dclass) = current_class; - current_class = (yyvsp[(3) - (7)].u.dclass); + current_class = (yyvsp[-4].u.dclass); } +#line 1770 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 32: - -/* Line 1464 of yacc.c */ -#line 305 "dcParser.yxx" +#line 311 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (dc_file == (DCFile *)NULL) { + if (dc_file == nullptr) { yyerror("No DCFile available, so no class names are predefined."); - (yyval.u.dclass) = NULL; + (yyval.u.dclass) = nullptr; } else { - DCClass *dclass = dc_file->get_class_by_name((yyvsp[(1) - (1)].str)); - if (dclass == (DCClass *)NULL) { + DCClass *dclass = dc_file->get_class_by_name((yyvsp[0].str)); + if (dclass == nullptr) { // Create a bogus class as a forward reference. - dclass = new DCClass(dc_file, (yyvsp[(1) - (1)].str), false, true); + dclass = new DCClass(dc_file, (yyvsp[0].str), false, true); dc_file->add_class(dclass); } if (dclass->is_struct()) { yyerror("struct name not allowed"); } - + (yyval.u.dclass) = dclass; } } +#line 1794 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 35: - -/* Line 1464 of yacc.c */ -#line 333 "dcParser.yxx" +#line 339 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (1)].u.dclass) != (DCClass *)NULL) { - current_class->add_parent((yyvsp[(1) - (1)].u.dclass)); + if ((yyvsp[0].u.dclass) != nullptr) { + current_class->add_parent((yyvsp[0].u.dclass)); } } +#line 1804 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 36: - -/* Line 1464 of yacc.c */ -#line 339 "dcParser.yxx" +#line 345 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { if (!dc_multiple_inheritance) { yyerror("Multiple inheritance is not supported without \"dc-multiple-inheritance 1\" in your Config.prc file."); } else { - if ((yyvsp[(3) - (3)].u.dclass) != (DCClass *)NULL) { - current_class->add_parent((yyvsp[(3) - (3)].u.dclass)); + if ((yyvsp[0].u.dclass) != nullptr) { + current_class->add_parent((yyvsp[0].u.dclass)); } } } +#line 1819 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 39: - -/* Line 1464 of yacc.c */ -#line 355 "dcParser.yxx" +#line 361 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(2) - (3)].u.field) == (DCField *)NULL) { + if ((yyvsp[-1].u.field) == nullptr) { // Pass this error up. - } else if (!current_class->add_field((yyvsp[(2) - (3)].u.field))) { - yyerror("Duplicate field name: " + (yyvsp[(2) - (3)].u.field)->get_name()); - } else if ((yyvsp[(2) - (3)].u.field)->get_number() < 0) { + } else if (!current_class->add_field((yyvsp[-1].u.field))) { + yyerror("Duplicate field name: " + (yyvsp[-1].u.field)->get_name()); + } else if ((yyvsp[-1].u.field)->get_number() < 0) { yyerror("A non-network field cannot be stored on a dclass"); } } +#line 1833 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 40: - -/* Line 1464 of yacc.c */ -#line 368 "dcParser.yxx" +#line 374 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (2)].u.field) != (DCField *)NULL) { - if ((yyvsp[(1) - (2)].u.field)->get_name().empty()) { + if ((yyvsp[-1].u.field) != nullptr) { + if ((yyvsp[-1].u.field)->get_name().empty()) { yyerror("Field name required."); } - (yyvsp[(1) - (2)].u.field)->copy_keywords(current_keyword_list); + (yyvsp[-1].u.field)->copy_keywords(current_keyword_list); } - (yyval.u.field) = (yyvsp[(1) - (2)].u.field); + (yyval.u.field) = (yyvsp[-1].u.field); } +#line 1847 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 42: - -/* Line 1464 of yacc.c */ -#line 379 "dcParser.yxx" +#line 385 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { yyerror("Unnamed parameters are not allowed on a dclass"); - if ((yyvsp[(1) - (2)].u.parameter) != (DCField *)NULL) { - (yyvsp[(1) - (2)].u.parameter)->copy_keywords(current_keyword_list); + if ((yyvsp[-1].u.parameter) != nullptr) { + (yyvsp[-1].u.parameter)->copy_keywords(current_keyword_list); } - (yyval.u.field) = (yyvsp[(1) - (2)].u.parameter); + (yyval.u.field) = (yyvsp[-1].u.parameter); } +#line 1859 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 43: - -/* Line 1464 of yacc.c */ -#line 387 "dcParser.yxx" +#line 393 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (2)].u.parameter) != (DCField *)NULL) { - (yyvsp[(1) - (2)].u.parameter)->copy_keywords(current_keyword_list); + if ((yyvsp[-1].u.parameter) != nullptr) { + (yyvsp[-1].u.parameter)->copy_keywords(current_keyword_list); } - (yyval.u.field) = (yyvsp[(1) - (2)].u.parameter); + (yyval.u.field) = (yyvsp[-1].u.parameter); } +#line 1870 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 44: - -/* Line 1464 of yacc.c */ -#line 397 "dcParser.yxx" +#line 403 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_class = new DCClass(dc_file, (yyvsp[(2) - (2)].str), true, false); + current_class = new DCClass(dc_file, (yyvsp[0].str), true, false); } +#line 1878 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 45: - -/* Line 1464 of yacc.c */ -#line 401 "dcParser.yxx" +#line 407 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.dclass) = current_class; - current_class = (yyvsp[(3) - (7)].u.dclass); + current_class = (yyvsp[-4].u.dclass); } +#line 1887 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 46: - -/* Line 1464 of yacc.c */ -#line 409 "dcParser.yxx" +#line 415 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (dc_file == (DCFile *)NULL) { + if (dc_file == nullptr) { yyerror("No DCFile available, so no struct names are predefined."); - (yyval.u.dclass) = NULL; + (yyval.u.dclass) = nullptr; } else { - DCClass *dstruct = dc_file->get_class_by_name((yyvsp[(1) - (1)].str)); - if (dstruct == (DCClass *)NULL) { + DCClass *dstruct = dc_file->get_class_by_name((yyvsp[0].str)); + if (dstruct == nullptr) { // Create a bogus class as a forward reference. - dstruct = new DCClass(dc_file, (yyvsp[(1) - (1)].str), false, true); + dstruct = new DCClass(dc_file, (yyvsp[0].str), false, true); dc_file->add_class(dstruct); } if (!dstruct->is_struct()) { yyerror("struct name required"); } - + (yyval.u.dclass) = dstruct; } } +#line 1911 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 49: - -/* Line 1464 of yacc.c */ -#line 437 "dcParser.yxx" +#line 443 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (1)].u.dclass) != (DCClass *)NULL) { - current_class->add_parent((yyvsp[(1) - (1)].u.dclass)); + if ((yyvsp[0].u.dclass) != nullptr) { + current_class->add_parent((yyvsp[0].u.dclass)); } } +#line 1921 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 50: - -/* Line 1464 of yacc.c */ -#line 443 "dcParser.yxx" +#line 449 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(3) - (3)].u.dclass) != (DCClass *)NULL) { - current_class->add_parent((yyvsp[(3) - (3)].u.dclass)); + if ((yyvsp[0].u.dclass) != nullptr) { + current_class->add_parent((yyvsp[0].u.dclass)); } } +#line 1931 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 53: - -/* Line 1464 of yacc.c */ -#line 454 "dcParser.yxx" +#line 460 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(2) - (3)].u.field) == (DCField *)NULL) { + if ((yyvsp[-1].u.field) == nullptr) { // Pass this error up. - } else if (!current_class->add_field((yyvsp[(2) - (3)].u.field))) { - yyerror("Duplicate field name: " + (yyvsp[(2) - (3)].u.field)->get_name()); + } else if (!current_class->add_field((yyvsp[-1].u.field))) { + yyerror("Duplicate field name: " + (yyvsp[-1].u.field)->get_name()); } } +#line 1943 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 54: - -/* Line 1464 of yacc.c */ -#line 465 "dcParser.yxx" +#line 471 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (2)].u.field)->get_name().empty()) { + if ((yyvsp[-1].u.field)->get_name().empty()) { yyerror("Field name required."); } - (yyval.u.field) = (yyvsp[(1) - (2)].u.field); + (yyval.u.field) = (yyvsp[-1].u.field); } +#line 1954 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 56: - -/* Line 1464 of yacc.c */ -#line 473 "dcParser.yxx" +#line 479 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.field) = (yyvsp[(1) - (2)].u.parameter); + (yyval.u.field) = (yyvsp[-1].u.parameter); } +#line 1962 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 57: - -/* Line 1464 of yacc.c */ -#line 477 "dcParser.yxx" +#line 483 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.field) = (yyvsp[(1) - (2)].u.parameter); + (yyval.u.field) = (yyvsp[-1].u.parameter); } +#line 1970 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 58: - -/* Line 1464 of yacc.c */ -#line 484 "dcParser.yxx" +#line 490 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (current_class == (DCClass *)NULL) { + if (current_class == nullptr) { yyerror("Cannot define a method outside of a struct or class."); DCClass *temp_class = new DCClass(dc_file, "temp", false, false); // memory leak. - current_atomic = new DCAtomicField((yyvsp[(1) - (2)].str), temp_class, false); + current_atomic = new DCAtomicField((yyvsp[-1].str), temp_class, false); } else { - current_atomic = new DCAtomicField((yyvsp[(1) - (2)].str), current_class, false); + current_atomic = new DCAtomicField((yyvsp[-1].str), current_class, false); } } +#line 1984 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 59: - -/* Line 1464 of yacc.c */ -#line 494 "dcParser.yxx" +#line 500 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.field) = current_atomic; - current_atomic = (yyvsp[(3) - (5)].u.atomic); + current_atomic = (yyvsp[-2].u.atomic); } +#line 1993 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 64: - -/* Line 1464 of yacc.c */ -#line 512 "dcParser.yxx" +#line 518 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (1)].u.parameter) != (DCParameter *)NULL) { - current_atomic->add_element((yyvsp[(1) - (1)].u.parameter)); + if ((yyvsp[0].u.parameter) != nullptr) { + current_atomic->add_element((yyvsp[0].u.parameter)); } } +#line 2003 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 65: - -/* Line 1464 of yacc.c */ -#line 521 "dcParser.yxx" +#line 527 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_parameter = (yyvsp[(1) - (1)].u.parameter); + current_parameter = (yyvsp[0].u.parameter); } +#line 2011 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 66: - -/* Line 1464 of yacc.c */ -#line 525 "dcParser.yxx" +#line 531 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.parameter) = (yyvsp[(3) - (3)].u.parameter); + (yyval.u.parameter) = (yyvsp[0].u.parameter); } +#line 2019 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 69: - -/* Line 1464 of yacc.c */ -#line 537 "dcParser.yxx" +#line 543 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_packer = &default_packer; current_packer->clear_data(); - if ((yyvsp[(1) - (2)].u.parameter) != (DCField *)NULL) { - current_packer->begin_pack((yyvsp[(1) - (2)].u.parameter)); + if ((yyvsp[-1].u.parameter) != nullptr) { + current_packer->begin_pack((yyvsp[-1].u.parameter)); } } +#line 2031 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 70: - -/* Line 1464 of yacc.c */ -#line 545 "dcParser.yxx" +#line 551 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { bool is_valid = false; - if ((yyvsp[(1) - (4)].u.parameter) != (DCField *)NULL) { - is_valid = (yyvsp[(1) - (4)].u.parameter)->is_valid(); + if ((yyvsp[-3].u.parameter) != nullptr) { + is_valid = (yyvsp[-3].u.parameter)->is_valid(); } if (current_packer->end_pack()) { - (yyvsp[(1) - (4)].u.parameter)->set_default_value(current_packer->get_string()); + (yyvsp[-3].u.parameter)->set_default_value(current_packer->get_bytes()); } else { if (is_valid) { @@ -2282,32 +2049,30 @@ yyreduce: // just ignore the default value in this case. } } +#line 2053 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 72: - -/* Line 1464 of yacc.c */ -#line 567 "dcParser.yxx" +#line 573 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_packer = &default_packer; current_packer->clear_data(); - if ((yyvsp[(1) - (2)].u.parameter) != (DCField *)NULL) { - current_packer->begin_pack((yyvsp[(1) - (2)].u.parameter)); + if ((yyvsp[-1].u.parameter) != nullptr) { + current_packer->begin_pack((yyvsp[-1].u.parameter)); } } +#line 2065 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 73: - -/* Line 1464 of yacc.c */ -#line 575 "dcParser.yxx" +#line 581 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { bool is_valid = false; - if ((yyvsp[(1) - (4)].u.parameter) != (DCField *)NULL) { - is_valid = (yyvsp[(1) - (4)].u.parameter)->is_valid(); + if ((yyvsp[-3].u.parameter) != nullptr) { + is_valid = (yyvsp[-3].u.parameter)->is_valid(); } if (current_packer->end_pack()) { - (yyvsp[(1) - (4)].u.parameter)->set_default_value(current_packer->get_string()); + (yyvsp[-3].u.parameter)->set_default_value(current_packer->get_bytes()); } else { if (is_valid) { @@ -2318,87 +2083,79 @@ yyreduce: // just ignore the default value in this case. } } +#line 2087 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 78: - -/* Line 1464 of yacc.c */ -#line 606 "dcParser.yxx" +#line 612 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.field) = (yyvsp[(1) - (1)].u.parameter); + (yyval.u.field) = (yyvsp[0].u.parameter); } +#line 2095 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 79: - -/* Line 1464 of yacc.c */ -#line 610 "dcParser.yxx" +#line 616 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.field) = (yyvsp[(1) - (1)].u.field); + (yyval.u.field) = (yyvsp[0].u.field); } +#line 2103 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 80: - -/* Line 1464 of yacc.c */ -#line 617 "dcParser.yxx" +#line 623 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.field) = (yyvsp[(1) - (2)].u.field); + (yyval.u.field) = (yyvsp[-1].u.field); } +#line 2111 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 81: - -/* Line 1464 of yacc.c */ -#line 621 "dcParser.yxx" +#line 627 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.field) = (yyvsp[(1) - (2)].u.parameter); + (yyval.u.field) = (yyvsp[-1].u.parameter); } +#line 2119 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 82: - -/* Line 1464 of yacc.c */ -#line 625 "dcParser.yxx" +#line 631 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.field) = (yyvsp[(1) - (2)].u.parameter); + (yyval.u.field) = (yyvsp[-1].u.parameter); } +#line 2127 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 83: - -/* Line 1464 of yacc.c */ -#line 632 "dcParser.yxx" +#line 638 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.parameter) = new DCSimpleParameter((yyvsp[(1) - (1)].u.subatomic)); + (yyval.u.parameter) = new DCSimpleParameter((yyvsp[0].u.subatomic)); } +#line 2135 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 84: - -/* Line 1464 of yacc.c */ -#line 636 "dcParser.yxx" +#line 642 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - DCSimpleParameter *simple_param = (yyvsp[(1) - (4)].u.parameter)->as_simple_parameter(); - nassertr(simple_param != (DCSimpleParameter *)NULL, 0); + DCSimpleParameter *simple_param = (yyvsp[-3].u.parameter)->as_simple_parameter(); + nassertr(simple_param != nullptr, 0); if (!simple_param->set_range(double_range)) { yyerror("Inappropriate range for type"); } (yyval.u.parameter) = simple_param; } +#line 2148 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 85: - -/* Line 1464 of yacc.c */ -#line 645 "dcParser.yxx" +#line 651 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - DCSimpleParameter *simple_param = (yyvsp[(1) - (3)].u.parameter)->as_simple_parameter(); - nassertr(simple_param != (DCSimpleParameter *)NULL, 0); + DCSimpleParameter *simple_param = (yyvsp[-2].u.parameter)->as_simple_parameter(); + nassertr(simple_param != nullptr, 0); if (!simple_param->is_numeric_type()) { yyerror("A divisor is only valid on a numeric type."); - } else if (!simple_param->set_divisor((yyvsp[(3) - (3)].u.s_uint))) { + } else if (!simple_param->set_divisor((yyvsp[0].u.s_uint))) { yyerror("Invalid divisor."); } else if (simple_param->has_modulus() && !simple_param->set_modulus(simple_param->get_modulus())) { @@ -2407,982 +2164,907 @@ yyreduce: } (yyval.u.parameter) = simple_param; } +#line 2168 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 86: - -/* Line 1464 of yacc.c */ -#line 661 "dcParser.yxx" - { - DCSimpleParameter *simple_param = (yyvsp[(1) - (3)].u.parameter)->as_simple_parameter(); - nassertr(simple_param != (DCSimpleParameter *)NULL, 0); +#line 667 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ + { + DCSimpleParameter *simple_param = (yyvsp[-2].u.parameter)->as_simple_parameter(); + nassertr(simple_param != nullptr, 0); if (!simple_param->is_numeric_type()) { yyerror("A divisor is only valid on a numeric type."); - } else if (!simple_param->set_modulus((yyvsp[(3) - (3)].u.real))) { + } else if (!simple_param->set_modulus((yyvsp[0].u.real))) { yyerror("Invalid modulus."); } (yyval.u.parameter) = simple_param; } +#line 2184 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 88: - -/* Line 1464 of yacc.c */ -#line 677 "dcParser.yxx" +#line 683 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (dc_file == (DCFile *)NULL) { + if (dc_file == nullptr) { yyerror("Invalid type."); - (yyval.u.parameter) = NULL; + (yyval.u.parameter) = nullptr; } else { - DCTypedef *dtypedef = dc_file->get_typedef_by_name((yyvsp[(1) - (1)].str)); - if (dtypedef == (DCTypedef *)NULL) { + DCTypedef *dtypedef = dc_file->get_typedef_by_name((yyvsp[0].str)); + if (dtypedef == nullptr) { // Maybe it's a class name. - DCClass *dclass = dc_file->get_class_by_name((yyvsp[(1) - (1)].str)); - if (dclass != (DCClass *)NULL) { + DCClass *dclass = dc_file->get_class_by_name((yyvsp[0].str)); + if (dclass != nullptr) { // Create an implicit typedef for this. dtypedef = new DCTypedef(new DCClassParameter(dclass), true); } else { // Maybe it's a switch name. - DCSwitch *dswitch = dc_file->get_switch_by_name((yyvsp[(1) - (1)].str)); - if (dswitch != (DCSwitch *)NULL) { + DCSwitch *dswitch = dc_file->get_switch_by_name((yyvsp[0].str)); + if (dswitch != nullptr) { // This also gets an implicit typedef. dtypedef = new DCTypedef(new DCSwitchParameter(dswitch), true); } else { // It's an undefined typedef. Create a bogus forward reference. - dtypedef = new DCTypedef((yyvsp[(1) - (1)].str)); + dtypedef = new DCTypedef((yyvsp[0].str)); } } - + dc_file->add_typedef(dtypedef); } - + (yyval.u.parameter) = dtypedef->make_new_parameter(); } } +#line 2220 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 89: - -/* Line 1464 of yacc.c */ -#line 709 "dcParser.yxx" +#line 715 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { // This is an inline struct definition. - if ((yyvsp[(1) - (1)].u.dclass) == (DCClass *)NULL) { - (yyval.u.parameter) = NULL; + if ((yyvsp[0].u.dclass) == nullptr) { + (yyval.u.parameter) = nullptr; } else { - if (dc_file != (DCFile *)NULL) { - dc_file->add_thing_to_delete((yyvsp[(1) - (1)].u.dclass)); + if (dc_file != nullptr) { + dc_file->add_thing_to_delete((yyvsp[0].u.dclass)); } else { // This is a memory leak--this happens when we put an anonymous // struct reference within the string passed to // DCPackerInterface::check_match(). Maybe it doesn't really matter. } - (yyval.u.parameter) = new DCClassParameter((yyvsp[(1) - (1)].u.dclass)); + (yyval.u.parameter) = new DCClassParameter((yyvsp[0].u.dclass)); } } +#line 2240 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 90: - -/* Line 1464 of yacc.c */ -#line 725 "dcParser.yxx" +#line 731 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { // This is an inline switch definition. - if ((yyvsp[(1) - (1)].u.dswitch) == (DCSwitch *)NULL) { - (yyval.u.parameter) = NULL; + if ((yyvsp[0].u.dswitch) == nullptr) { + (yyval.u.parameter) = nullptr; } else { - if (dc_file != (DCFile *)NULL) { - dc_file->add_thing_to_delete((yyvsp[(1) - (1)].u.dswitch)); + if (dc_file != nullptr) { + dc_file->add_thing_to_delete((yyvsp[0].u.dswitch)); } else { // This is a memory leak--this happens when we put an anonymous // switch reference within the string passed to // DCPackerInterface::check_match(). Maybe it doesn't really matter. } - (yyval.u.parameter) = new DCSwitchParameter((yyvsp[(1) - (1)].u.dswitch)); + (yyval.u.parameter) = new DCSwitchParameter((yyvsp[0].u.dswitch)); } } +#line 2260 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 91: - -/* Line 1464 of yacc.c */ -#line 744 "dcParser.yxx" +#line 750 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { double_range.clear(); } +#line 2268 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 92: - -/* Line 1464 of yacc.c */ -#line 748 "dcParser.yxx" +#line 754 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { double_range.clear(); - if (!double_range.add_range((yyvsp[(1) - (1)].u.real), (yyvsp[(1) - (1)].u.real))) { + if (!double_range.add_range((yyvsp[0].u.real), (yyvsp[0].u.real))) { yyerror("Overlapping range"); } } +#line 2279 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 93: - -/* Line 1464 of yacc.c */ -#line 755 "dcParser.yxx" +#line 761 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { double_range.clear(); - if (!double_range.add_range((yyvsp[(1) - (3)].u.real), (yyvsp[(3) - (3)].u.real))) { + if (!double_range.add_range((yyvsp[-2].u.real), (yyvsp[0].u.real))) { yyerror("Overlapping range"); } } +#line 2290 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 94: - -/* Line 1464 of yacc.c */ -#line 762 "dcParser.yxx" +#line 768 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { double_range.clear(); - if ((yyvsp[(2) - (2)].u.real) >= 0) { + if ((yyvsp[0].u.real) >= 0) { yyerror("Syntax error"); - } else if (!double_range.add_range((yyvsp[(1) - (2)].u.real), -(yyvsp[(2) - (2)].u.real))) { + } else if (!double_range.add_range((yyvsp[-1].u.real), -(yyvsp[0].u.real))) { yyerror("Overlapping range"); } } +#line 2303 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 95: - -/* Line 1464 of yacc.c */ -#line 771 "dcParser.yxx" +#line 777 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (!double_range.add_range((yyvsp[(3) - (3)].u.real), (yyvsp[(3) - (3)].u.real))) { + if (!double_range.add_range((yyvsp[0].u.real), (yyvsp[0].u.real))) { yyerror("Overlapping range"); } } +#line 2313 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 96: - -/* Line 1464 of yacc.c */ -#line 777 "dcParser.yxx" +#line 783 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (!double_range.add_range((yyvsp[(3) - (5)].u.real), (yyvsp[(5) - (5)].u.real))) { + if (!double_range.add_range((yyvsp[-2].u.real), (yyvsp[0].u.real))) { yyerror("Overlapping range"); } } +#line 2323 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 97: - -/* Line 1464 of yacc.c */ -#line 783 "dcParser.yxx" +#line 789 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(4) - (4)].u.real) >= 0) { + if ((yyvsp[0].u.real) >= 0) { yyerror("Syntax error"); - } else if (!double_range.add_range((yyvsp[(3) - (4)].u.real), -(yyvsp[(4) - (4)].u.real))) { + } else if (!double_range.add_range((yyvsp[-1].u.real), -(yyvsp[0].u.real))) { yyerror("Overlapping range"); } } +#line 2335 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 98: - -/* Line 1464 of yacc.c */ -#line 794 "dcParser.yxx" +#line 800 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { uint_range.clear(); } +#line 2343 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 99: - -/* Line 1464 of yacc.c */ -#line 798 "dcParser.yxx" +#line 804 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { uint_range.clear(); - if (!uint_range.add_range((yyvsp[(1) - (1)].u.s_uint), (yyvsp[(1) - (1)].u.s_uint))) { + if (!uint_range.add_range((yyvsp[0].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } +#line 2354 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 100: - -/* Line 1464 of yacc.c */ -#line 805 "dcParser.yxx" +#line 811 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { uint_range.clear(); - if (!uint_range.add_range((yyvsp[(1) - (3)].u.s_uint), (yyvsp[(3) - (3)].u.s_uint))) { + if (!uint_range.add_range((yyvsp[-2].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } +#line 2365 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 101: - -/* Line 1464 of yacc.c */ -#line 812 "dcParser.yxx" +#line 818 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { uint_range.clear(); - if (!uint_range.add_range((yyvsp[(1) - (2)].u.s_uint), (yyvsp[(2) - (2)].u.s_uint))) { + if (!uint_range.add_range((yyvsp[-1].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } +#line 2376 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 102: - -/* Line 1464 of yacc.c */ -#line 819 "dcParser.yxx" +#line 825 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (!uint_range.add_range((yyvsp[(3) - (3)].u.s_uint), (yyvsp[(3) - (3)].u.s_uint))) { + if (!uint_range.add_range((yyvsp[0].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } +#line 2386 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 103: - -/* Line 1464 of yacc.c */ -#line 825 "dcParser.yxx" +#line 831 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (!uint_range.add_range((yyvsp[(3) - (5)].u.s_uint), (yyvsp[(5) - (5)].u.s_uint))) { + if (!uint_range.add_range((yyvsp[-2].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } +#line 2396 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 104: - -/* Line 1464 of yacc.c */ -#line 831 "dcParser.yxx" +#line 837 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if (!uint_range.add_range((yyvsp[(3) - (4)].u.s_uint), (yyvsp[(4) - (4)].u.s_uint))) { + if (!uint_range.add_range((yyvsp[-1].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } +#line 2406 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 106: - -/* Line 1464 of yacc.c */ -#line 841 "dcParser.yxx" +#line 847 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (4)].u.parameter) == (DCParameter *)NULL) { - (yyval.u.parameter) = NULL; + if ((yyvsp[-3].u.parameter) == nullptr) { + (yyval.u.parameter) = nullptr; } else { - (yyval.u.parameter) = (yyvsp[(1) - (4)].u.parameter)->append_array_specification(uint_range); + (yyval.u.parameter) = (yyvsp[-3].u.parameter)->append_array_specification(uint_range); } } +#line 2418 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 107: - -/* Line 1464 of yacc.c */ -#line 852 "dcParser.yxx" +#line 858 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_parameter->set_name((yyvsp[(1) - (1)].str)); + current_parameter->set_name((yyvsp[0].str)); (yyval.u.parameter) = current_parameter; } +#line 2427 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 108: - -/* Line 1464 of yacc.c */ -#line 857 "dcParser.yxx" +#line 863 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - DCSimpleParameter *simple_param = (yyvsp[(1) - (3)].u.parameter)->as_simple_parameter(); - if (simple_param == NULL || simple_param->get_typedef() != (DCTypedef *)NULL) { + DCSimpleParameter *simple_param = (yyvsp[-2].u.parameter)->as_simple_parameter(); + if (simple_param == nullptr || simple_param->get_typedef() != nullptr) { yyerror("A divisor is only allowed on a primitive type."); } else if (!simple_param->is_numeric_type()) { yyerror("A divisor is only valid on a numeric type."); } else { - if (!simple_param->set_divisor((yyvsp[(3) - (3)].u.s_uint))) { + if (!simple_param->set_divisor((yyvsp[0].u.s_uint))) { yyerror("Invalid divisor."); } } } +#line 2446 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 109: - -/* Line 1464 of yacc.c */ -#line 872 "dcParser.yxx" +#line 878 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - DCSimpleParameter *simple_param = (yyvsp[(1) - (3)].u.parameter)->as_simple_parameter(); - if (simple_param == NULL || simple_param->get_typedef() != (DCTypedef *)NULL) { + DCSimpleParameter *simple_param = (yyvsp[-2].u.parameter)->as_simple_parameter(); + if (simple_param == nullptr || simple_param->get_typedef() != nullptr) { yyerror("A modulus is only allowed on a primitive type."); } else if (!simple_param->is_numeric_type()) { yyerror("A modulus is only valid on a numeric type."); } else { - if (!simple_param->set_modulus((yyvsp[(3) - (3)].u.real))) { + if (!simple_param->set_modulus((yyvsp[0].u.real))) { yyerror("Invalid modulus."); } } } +#line 2465 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 110: - -/* Line 1464 of yacc.c */ -#line 887 "dcParser.yxx" +#line 893 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.parameter) = (yyvsp[(1) - (4)].u.parameter)->append_array_specification(uint_range); + (yyval.u.parameter) = (yyvsp[-3].u.parameter)->append_array_specification(uint_range); } +#line 2473 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 111: - -/* Line 1464 of yacc.c */ -#line 894 "dcParser.yxx" +#line 900 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (1)].str).length() != 1) { + if ((yyvsp[0].str).length() != 1) { yyerror("Single character required."); (yyval.u.s_uint) = 0; } else { - (yyval.u.s_uint) = (unsigned char)(yyvsp[(1) - (1)].str)[0]; + (yyval.u.s_uint) = (unsigned char)(yyvsp[0].str)[0]; } } +#line 2486 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 113: - -/* Line 1464 of yacc.c */ -#line 907 "dcParser.yxx" +#line 913 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.s_uint) = (unsigned int)(yyvsp[(1) - (1)].u.uint64); - if ((yyval.u.s_uint) != (yyvsp[(1) - (1)].u.uint64)) { + (yyval.u.s_uint) = (unsigned int)(yyvsp[0].u.uint64); + if ((yyval.u.s_uint) != (yyvsp[0].u.uint64)) { yyerror("Number out of range."); (yyval.u.s_uint) = 1; } } +#line 2498 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 114: - -/* Line 1464 of yacc.c */ -#line 918 "dcParser.yxx" +#line 924 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.s_uint) = (unsigned int)-(yyvsp[(1) - (1)].u.int64); - if ((yyvsp[(1) - (1)].u.int64) >= 0) { + (yyval.u.s_uint) = (unsigned int)-(yyvsp[0].u.int64); + if ((yyvsp[0].u.int64) >= 0) { yyerror("Syntax error."); - } else if ((yyval.u.s_uint) != -(yyvsp[(1) - (1)].u.int64)) { + } else if ((yyval.u.s_uint) != -(yyvsp[0].u.int64)) { yyerror("Number out of range."); (yyval.u.s_uint) = 1; } } +#line 2513 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 117: - -/* Line 1464 of yacc.c */ -#line 940 "dcParser.yxx" +#line 946 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.real) = (double)(yyvsp[(1) - (1)].u.uint64); + (yyval.u.real) = (double)(yyvsp[0].u.uint64); } +#line 2521 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 118: - -/* Line 1464 of yacc.c */ -#line 944 "dcParser.yxx" +#line 950 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.real) = (double)(yyvsp[(1) - (1)].u.int64); + (yyval.u.real) = (double)(yyvsp[0].u.int64); } +#line 2529 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 120: - -/* Line 1464 of yacc.c */ -#line 952 "dcParser.yxx" +#line 958 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (1)].str).length() != 1) { + if ((yyvsp[0].str).length() != 1) { yyerror("Single character required."); (yyval.u.real) = 0; } else { - (yyval.u.real) = (double)(unsigned char)(yyvsp[(1) - (1)].str)[0]; + (yyval.u.real) = (double)(unsigned char)(yyvsp[0].str)[0]; } } +#line 2542 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 122: - -/* Line 1464 of yacc.c */ -#line 966 "dcParser.yxx" +#line 972 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { } +#line 2549 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 123: - -/* Line 1464 of yacc.c */ -#line 969 "dcParser.yxx" +#line 975 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (2)].str) != current_packer->get_current_field_name()) { + if ((yyvsp[-1].str) != current_packer->get_current_field_name()) { ostringstream strm; - strm << "Got '" << (yyvsp[(1) - (2)].str) << "', expected '" + strm << "Got '" << (yyvsp[-1].str) << "', expected '" << current_packer->get_current_field_name() << "'"; yyerror(strm.str()); } } +#line 2562 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 124: - -/* Line 1464 of yacc.c */ -#line 978 "dcParser.yxx" +#line 984 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { } +#line 2569 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 125: - -/* Line 1464 of yacc.c */ -#line 984 "dcParser.yxx" +#line 990 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_packer->pack_int64((yyvsp[(1) - (1)].u.int64)); + current_packer->pack_int64((yyvsp[0].u.int64)); } +#line 2577 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 126: - -/* Line 1464 of yacc.c */ -#line 988 "dcParser.yxx" +#line 994 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_packer->pack_uint64((yyvsp[(1) - (1)].u.uint64)); + current_packer->pack_uint64((yyvsp[0].u.uint64)); } +#line 2585 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 127: - -/* Line 1464 of yacc.c */ -#line 992 "dcParser.yxx" +#line 998 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_packer->pack_double((yyvsp[(1) - (1)].u.real)); + current_packer->pack_double((yyvsp[0].u.real)); } +#line 2593 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 128: - -/* Line 1464 of yacc.c */ -#line 996 "dcParser.yxx" +#line 1002 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_packer->pack_string((yyvsp[(1) - (1)].str)); + current_packer->pack_string((yyvsp[0].str)); } +#line 2601 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 129: - -/* Line 1464 of yacc.c */ -#line 1000 "dcParser.yxx" +#line 1006 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_packer->pack_literal_value((yyvsp[(1) - (1)].str)); + current_packer->pack_literal_value((yyvsp[0].bytes)); } +#line 2609 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 130: - -/* Line 1464 of yacc.c */ -#line 1004 "dcParser.yxx" +#line 1010 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_packer->push(); } +#line 2617 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 131: - -/* Line 1464 of yacc.c */ -#line 1008 "dcParser.yxx" +#line 1014 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_packer->pop(); } +#line 2625 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 132: - -/* Line 1464 of yacc.c */ -#line 1012 "dcParser.yxx" +#line 1018 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_packer->push(); } +#line 2633 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 133: - -/* Line 1464 of yacc.c */ -#line 1016 "dcParser.yxx" +#line 1022 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_packer->pop(); } +#line 2641 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 134: - -/* Line 1464 of yacc.c */ -#line 1020 "dcParser.yxx" +#line 1026 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_packer->push(); } +#line 2649 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 135: - -/* Line 1464 of yacc.c */ -#line 1024 "dcParser.yxx" +#line 1030 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_packer->pop(); } +#line 2657 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 136: - -/* Line 1464 of yacc.c */ -#line 1028 "dcParser.yxx" +#line 1034 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - for (unsigned int i = 0; i < (yyvsp[(3) - (3)].u.s_uint); i++) { - current_packer->pack_int64((yyvsp[(1) - (3)].u.int64)); + for (unsigned int i = 0; i < (yyvsp[0].u.s_uint); i++) { + current_packer->pack_int64((yyvsp[-2].u.int64)); } } +#line 2667 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 137: - -/* Line 1464 of yacc.c */ -#line 1034 "dcParser.yxx" +#line 1040 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - for (unsigned int i = 0; i < (yyvsp[(3) - (3)].u.s_uint); i++) { - current_packer->pack_uint64((yyvsp[(1) - (3)].u.uint64)); + for (unsigned int i = 0; i < (yyvsp[0].u.s_uint); i++) { + current_packer->pack_uint64((yyvsp[-2].u.uint64)); } } +#line 2677 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 138: - -/* Line 1464 of yacc.c */ -#line 1040 "dcParser.yxx" +#line 1046 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - for (unsigned int i = 0; i < (yyvsp[(3) - (3)].u.s_uint); i++) { - current_packer->pack_double((yyvsp[(1) - (3)].u.real)); + for (unsigned int i = 0; i < (yyvsp[0].u.s_uint); i++) { + current_packer->pack_double((yyvsp[-2].u.real)); } } +#line 2687 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 139: - -/* Line 1464 of yacc.c */ -#line 1046 "dcParser.yxx" +#line 1052 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - for (unsigned int i = 0; i < (yyvsp[(3) - (3)].u.s_uint); i++) { - current_packer->pack_literal_value((yyvsp[(1) - (3)].str)); + for (unsigned int i = 0; i < (yyvsp[0].u.s_uint); i++) { + current_packer->pack_literal_value((yyvsp[-2].bytes)); } } +#line 2697 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 146: - -/* Line 1464 of yacc.c */ -#line 1070 "dcParser.yxx" +#line 1076 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_int8; } +#line 2705 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 147: - -/* Line 1464 of yacc.c */ -#line 1074 "dcParser.yxx" +#line 1080 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_int16; } +#line 2713 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 148: - -/* Line 1464 of yacc.c */ -#line 1078 "dcParser.yxx" +#line 1084 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_int32; } +#line 2721 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 149: - -/* Line 1464 of yacc.c */ -#line 1082 "dcParser.yxx" +#line 1088 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_int64; } +#line 2729 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 150: - -/* Line 1464 of yacc.c */ -#line 1086 "dcParser.yxx" +#line 1092 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_uint8; } +#line 2737 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 151: - -/* Line 1464 of yacc.c */ -#line 1090 "dcParser.yxx" +#line 1096 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_uint16; } +#line 2745 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 152: - -/* Line 1464 of yacc.c */ -#line 1094 "dcParser.yxx" +#line 1100 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_uint32; } +#line 2753 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 153: - -/* Line 1464 of yacc.c */ -#line 1098 "dcParser.yxx" +#line 1104 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_uint64; } +#line 2761 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 154: - -/* Line 1464 of yacc.c */ -#line 1102 "dcParser.yxx" +#line 1108 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_float64; } +#line 2769 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 155: - -/* Line 1464 of yacc.c */ -#line 1106 "dcParser.yxx" +#line 1112 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_string; } +#line 2777 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 156: - -/* Line 1464 of yacc.c */ -#line 1110 "dcParser.yxx" +#line 1116 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_blob; } +#line 2785 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 157: - -/* Line 1464 of yacc.c */ -#line 1114 "dcParser.yxx" +#line 1120 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_blob32; } +#line 2793 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 158: - -/* Line 1464 of yacc.c */ -#line 1118 "dcParser.yxx" +#line 1124 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_int8array; } +#line 2801 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 159: - -/* Line 1464 of yacc.c */ -#line 1122 "dcParser.yxx" +#line 1128 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_int16array; } +#line 2809 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 160: - -/* Line 1464 of yacc.c */ -#line 1126 "dcParser.yxx" +#line 1132 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_int32array; } +#line 2817 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 161: - -/* Line 1464 of yacc.c */ -#line 1130 "dcParser.yxx" +#line 1136 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_uint8array; } +#line 2825 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 162: - -/* Line 1464 of yacc.c */ -#line 1134 "dcParser.yxx" +#line 1140 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_uint16array; } +#line 2833 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 163: - -/* Line 1464 of yacc.c */ -#line 1138 "dcParser.yxx" +#line 1144 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_uint32array; } +#line 2841 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 164: - -/* Line 1464 of yacc.c */ -#line 1142 "dcParser.yxx" +#line 1148 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_uint32uint8array; } +#line 2849 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 165: - -/* Line 1464 of yacc.c */ -#line 1146 "dcParser.yxx" +#line 1152 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.subatomic) = ST_char; } +#line 2857 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 166: - -/* Line 1464 of yacc.c */ -#line 1153 "dcParser.yxx" +#line 1159 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_keyword_list.clear_keywords(); } +#line 2865 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 167: - -/* Line 1464 of yacc.c */ -#line 1157 "dcParser.yxx" +#line 1163 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_keyword_list.add_keyword((yyvsp[(2) - (2)].u.keyword)); + current_keyword_list.add_keyword((yyvsp[0].u.keyword)); } +#line 2873 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 168: - -/* Line 1464 of yacc.c */ -#line 1164 "dcParser.yxx" +#line 1170 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { if (current_keyword_list.get_num_keywords() != 0) { yyerror("Communication keywords are not allowed here."); } } +#line 2883 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 169: - -/* Line 1464 of yacc.c */ -#line 1173 "dcParser.yxx" +#line 1179 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_molecular = new DCMolecularField((yyvsp[(1) - (2)].str), current_class); + current_molecular = new DCMolecularField((yyvsp[-1].str), current_class); } +#line 2891 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 170: - -/* Line 1464 of yacc.c */ -#line 1177 "dcParser.yxx" +#line 1183 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.field) = current_molecular; } +#line 2899 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 171: - -/* Line 1464 of yacc.c */ -#line 1184 "dcParser.yxx" +#line 1190 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - DCField *field = current_class->get_field_by_name((yyvsp[(1) - (1)].str)); - (yyval.u.atomic) = (DCAtomicField *)NULL; - if (field == (DCField *)NULL) { + DCField *field = current_class->get_field_by_name((yyvsp[0].str)); + (yyval.u.atomic) = nullptr; + if (field == nullptr) { // Maybe the field is unknown because the class is partially // bogus. In that case, allow it for now; create a bogus field as // a placeholder. if (current_class->inherits_from_bogus_class()) { - (yyval.u.atomic) = new DCAtomicField((yyvsp[(1) - (1)].str), current_class, true); + (yyval.u.atomic) = new DCAtomicField((yyvsp[0].str), current_class, true); current_class->add_field((yyval.u.atomic)); } else { // Nope, it's a fully-defined class, so this is a real error. - yyerror("Unknown field: " + (yyvsp[(1) - (1)].str)); + yyerror("Unknown field: " + (yyvsp[0].str)); } } else { (yyval.u.atomic) = field->as_atomic_field(); - if ((yyval.u.atomic) == (DCAtomicField *)NULL) { - yyerror("Not an atomic field: " + (yyvsp[(1) - (1)].str)); + if ((yyval.u.atomic) == nullptr) { + yyerror("Not an atomic field: " + (yyvsp[0].str)); } } } +#line 2927 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 172: - -/* Line 1464 of yacc.c */ -#line 1211 "dcParser.yxx" +#line 1217 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(1) - (1)].u.atomic) != (DCAtomicField *)NULL) { - current_molecular->add_atomic((yyvsp[(1) - (1)].u.atomic)); + if ((yyvsp[0].u.atomic) != nullptr) { + current_molecular->add_atomic((yyvsp[0].u.atomic)); } } +#line 2937 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 173: - -/* Line 1464 of yacc.c */ -#line 1217 "dcParser.yxx" +#line 1223 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - if ((yyvsp[(3) - (3)].u.atomic) != (DCAtomicField *)NULL) { - current_molecular->add_atomic((yyvsp[(3) - (3)].u.atomic)); - if (!(yyvsp[(3) - (3)].u.atomic)->is_bogus_field() && !current_molecular->compare_keywords(*(yyvsp[(3) - (3)].u.atomic))) { - yyerror("Mismatched keywords in molecule between " + + if ((yyvsp[0].u.atomic) != nullptr) { + current_molecular->add_atomic((yyvsp[0].u.atomic)); + if (!(yyvsp[0].u.atomic)->is_bogus_field() && !current_molecular->compare_keywords(*(yyvsp[0].u.atomic))) { + yyerror("Mismatched keywords in molecule between " + current_molecular->get_atomic(0)->get_name() + " and " + - (yyvsp[(3) - (3)].u.atomic)->get_name()); + (yyvsp[0].u.atomic)->get_name()); } } } +#line 2952 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 174: - -/* Line 1464 of yacc.c */ -#line 1231 "dcParser.yxx" +#line 1237 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.str) = ""; } +#line 2960 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 176: - -/* Line 1464 of yacc.c */ -#line 1239 "dcParser.yxx" +#line 1245 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - current_switch = new DCSwitch((yyvsp[(2) - (6)].str), (yyvsp[(4) - (6)].u.field)); + current_switch = new DCSwitch((yyvsp[-4].str), (yyvsp[-2].u.field)); } +#line 2968 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 177: - -/* Line 1464 of yacc.c */ -#line 1243 "dcParser.yxx" +#line 1249 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { (yyval.u.dswitch) = current_switch; - current_switch = (DCSwitch *)(yyvsp[(7) - (9)].u.parameter); + current_switch = (DCSwitch *)(yyvsp[-2].u.parameter); } +#line 2977 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 183: - -/* Line 1464 of yacc.c */ -#line 1257 "dcParser.yxx" +#line 1263 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { if (!current_switch->is_field_valid()) { yyerror("case declaration required before first element"); - } else if ((yyvsp[(2) - (3)].u.field) != (DCField *)NULL) { - if (!current_switch->add_field((yyvsp[(2) - (3)].u.field))) { - yyerror("Duplicate field name: " + (yyvsp[(2) - (3)].u.field)->get_name()); + } else if ((yyvsp[-1].u.field) != nullptr) { + if (!current_switch->add_field((yyvsp[-1].u.field))) { + yyerror("Duplicate field name: " + (yyvsp[-1].u.field)->get_name()); } } } +#line 2991 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 184: - -/* Line 1464 of yacc.c */ -#line 1270 "dcParser.yxx" +#line 1276 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_packer = &default_packer; current_packer->clear_data(); current_packer->begin_pack(current_switch->get_key_parameter()); } +#line 3001 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 185: - -/* Line 1464 of yacc.c */ -#line 1276 "dcParser.yxx" +#line 1282 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { if (!current_packer->end_pack()) { yyerror("Invalid value for switch parameter"); current_switch->add_invalid_case(); } else { - int case_index = current_switch->add_case(current_packer->get_string()); + int case_index = current_switch->add_case(current_packer->get_bytes()); if (case_index == -1) { yyerror("Duplicate case value"); } } } +#line 3017 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 186: - -/* Line 1464 of yacc.c */ -#line 1291 "dcParser.yxx" +#line 1297 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { if (!current_switch->add_default()) { yyerror("Default case already defined"); } } +#line 3027 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 187: - -/* Line 1464 of yacc.c */ -#line 1300 "dcParser.yxx" +#line 1306 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { current_switch->add_break(); } +#line 3035 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 188: - -/* Line 1464 of yacc.c */ -#line 1307 "dcParser.yxx" +#line 1313 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.field) = (yyvsp[(1) - (1)].u.parameter); + (yyval.u.field) = (yyvsp[0].u.parameter); } +#line 3043 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; case 189: - -/* Line 1464 of yacc.c */ -#line 1311 "dcParser.yxx" +#line 1317 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ { - (yyval.u.field) = (yyvsp[(1) - (1)].u.parameter); + (yyval.u.field) = (yyvsp[0].u.parameter); } +#line 3051 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ break; - -/* Line 1464 of yacc.c */ -#line 3380 "y.tab.c" +#line 3055 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ default: break; } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); YYPOPSTACK (yylen); @@ -3391,7 +3073,7 @@ yyreduce: *++yyvsp = yyval; - /* Now `shift' the result of the reduction. Determine what state + /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ @@ -3406,10 +3088,14 @@ yyreduce: goto yynewstate; -/*------------------------------------. -| yyerrlab -- here on detecting error | -`------------------------------------*/ +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); + /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { @@ -3417,37 +3103,36 @@ yyerrlab: #if ! YYERROR_VERBOSE yyerror (YY_("syntax error")); #else +# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ + yyssp, yytoken) { - YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); - if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) - { - YYSIZE_T yyalloc = 2 * yysize; - if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) - yyalloc = YYSTACK_ALLOC_MAXIMUM; - if (yymsg != yymsgbuf) - YYSTACK_FREE (yymsg); - yymsg = (char *) YYSTACK_ALLOC (yyalloc); - if (yymsg) - yymsg_alloc = yyalloc; - else - { - yymsg = yymsgbuf; - yymsg_alloc = sizeof yymsgbuf; - } - } - - if (0 < yysize && yysize <= yymsg_alloc) - { - (void) yysyntax_error (yymsg, yystate, yychar); - yyerror (yymsg); - } - else - { - yyerror (YY_("syntax error")); - if (yysize != 0) - goto yyexhaustedlab; - } + char const *yymsgp = YY_("syntax error"); + int yysyntax_error_status; + yysyntax_error_status = YYSYNTAX_ERROR; + if (yysyntax_error_status == 0) + yymsgp = yymsg; + else if (yysyntax_error_status == 1) + { + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); + if (!yymsg) + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + yysyntax_error_status = 2; + } + else + { + yysyntax_error_status = YYSYNTAX_ERROR; + yymsgp = yymsg; + } + } + yyerror (yymsgp); + if (yysyntax_error_status == 2) + goto yyexhaustedlab; } +# undef YYSYNTAX_ERROR #endif } @@ -3456,20 +3141,20 @@ yyerrlab: if (yyerrstatus == 3) { /* If just tried and failed to reuse lookahead token after an - error, discard it. */ + error, discard it. */ if (yychar <= YYEOF) - { - /* Return failure if at end of input. */ - if (yychar == YYEOF) - YYABORT; - } + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } else - { - yydestruct ("Error: discarding", - yytoken, &yylval); - yychar = YYEMPTY; - } + { + yydestruct ("Error: discarding", + yytoken, &yylval); + yychar = YYEMPTY; + } } /* Else will try to reuse lookahead token after shifting the error @@ -3488,7 +3173,7 @@ yyerrorlab: if (/*CONSTCOND*/ 0) goto yyerrorlab; - /* Do not reclaim the symbols of the rule which action triggered + /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; @@ -3501,35 +3186,37 @@ yyerrorlab: | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ yyerrlab1: - yyerrstatus = 3; /* Each real token shifted decrements this. */ + yyerrstatus = 3; /* Each real token shifted decrements this. */ for (;;) { yyn = yypact[yystate]; - if (yyn != YYPACT_NINF) - { - yyn += YYTERROR; - if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) - { - yyn = yytable[yyn]; - if (0 < yyn) - break; - } - } + if (!yypact_value_is_default (yyn)) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } /* Pop the current state because it cannot handle the error token. */ if (yyssp == yyss) - YYABORT; + YYABORT; yydestruct ("Error: popping", - yystos[yystate], yyvsp); + yystos[yystate], yyvsp); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); } + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END /* Shift the error token. */ @@ -3553,7 +3240,7 @@ yyabortlab: yyresult = 1; goto yyreturn; -#if !defined(yyoverflow) || YYERROR_VERBOSE +#if !defined yyoverflow || YYERROR_VERBOSE /*-------------------------------------------------. | yyexhaustedlab -- memory exhaustion comes here. | `-------------------------------------------------*/ @@ -3565,16 +3252,21 @@ yyexhaustedlab: yyreturn: if (yychar != YYEMPTY) - yydestruct ("Cleanup: discarding lookahead", - yytoken, &yylval); - /* Do not reclaim the symbols of the rule which action triggered + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval); + } + /* Do not reclaim the symbols of the rule whose action triggered this YYABORT or YYACCEPT. */ YYPOPSTACK (yylen); YY_STACK_PRINT (yyss, yyssp); while (yyssp != yyss) { yydestruct ("Cleanup: popping", - yystos[*yyssp], yyvsp); + yystos[*yyssp], yyvsp); YYPOPSTACK (1); } #ifndef yyoverflow @@ -3585,9 +3277,5 @@ yyreturn: if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); #endif - /* Make sure YYID is used. */ - return YYID (yyresult); + return yyresult; } - - - diff --git a/direct/src/dcparser/dcParser.h.prebuilt b/direct/src/dcparser/dcParser.h.prebuilt index 680eeff59c..7b46c4c7b1 100644 --- a/direct/src/dcparser/dcParser.h.prebuilt +++ b/direct/src/dcparser/dcParser.h.prebuilt @@ -1,20 +1,19 @@ -/* A Bison parser, made by GNU Bison 2.4.2. */ +/* A Bison parser, made by GNU Bison 3.0.4. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. -/* Skeleton interface for Bison's Yacc-like parsers in C - - Copyright (C) 1984, 1989-1990, 2000-2006, 2009-2010 Free Software - Foundation, Inc. - This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program. If not, see . */ @@ -27,66 +26,74 @@ special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. - + This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ +#ifndef YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED +# define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int dcyydebug; +#endif -/* Tokens. */ +/* Token type. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE - /* Put the tokens into the symbol table, so that GDB and other debuggers - know about them. */ - enum yytokentype { - UNSIGNED_INTEGER = 258, - SIGNED_INTEGER = 259, - REAL = 260, - STRING = 261, - HEX_STRING = 262, - IDENTIFIER = 263, - KEYWORD = 264, - KW_DCLASS = 265, - KW_STRUCT = 266, - KW_FROM = 267, - KW_IMPORT = 268, - KW_TYPEDEF = 269, - KW_KEYWORD = 270, - KW_SWITCH = 271, - KW_CASE = 272, - KW_DEFAULT = 273, - KW_BREAK = 274, - KW_INT8 = 275, - KW_INT16 = 276, - KW_INT32 = 277, - KW_INT64 = 278, - KW_UINT8 = 279, - KW_UINT16 = 280, - KW_UINT32 = 281, - KW_UINT64 = 282, - KW_FLOAT64 = 283, - KW_STRING = 284, - KW_BLOB = 285, - KW_BLOB32 = 286, - KW_INT8ARRAY = 287, - KW_INT16ARRAY = 288, - KW_INT32ARRAY = 289, - KW_UINT8ARRAY = 290, - KW_UINT16ARRAY = 291, - KW_UINT32ARRAY = 292, - KW_UINT32UINT8ARRAY = 293, - KW_CHAR = 294, - START_DC = 295, - START_PARAMETER_VALUE = 296, - START_PARAMETER_DESCRIPTION = 297 - }; + enum yytokentype + { + UNSIGNED_INTEGER = 258, + SIGNED_INTEGER = 259, + REAL = 260, + STRING = 261, + IDENTIFIER = 262, + HEX_STRING = 263, + KEYWORD = 264, + KW_DCLASS = 265, + KW_STRUCT = 266, + KW_FROM = 267, + KW_IMPORT = 268, + KW_TYPEDEF = 269, + KW_KEYWORD = 270, + KW_SWITCH = 271, + KW_CASE = 272, + KW_DEFAULT = 273, + KW_BREAK = 274, + KW_INT8 = 275, + KW_INT16 = 276, + KW_INT32 = 277, + KW_INT64 = 278, + KW_UINT8 = 279, + KW_UINT16 = 280, + KW_UINT32 = 281, + KW_UINT64 = 282, + KW_FLOAT64 = 283, + KW_STRING = 284, + KW_BLOB = 285, + KW_BLOB32 = 286, + KW_INT8ARRAY = 287, + KW_INT16ARRAY = 288, + KW_INT32ARRAY = 289, + KW_UINT8ARRAY = 290, + KW_UINT16ARRAY = 291, + KW_UINT32ARRAY = 292, + KW_UINT32UINT8ARRAY = 293, + KW_CHAR = 294, + START_DC = 295, + START_PARAMETER_VALUE = 296, + START_PARAMETER_DESCRIPTION = 297 + }; #endif /* Tokens. */ #define UNSIGNED_INTEGER 258 #define SIGNED_INTEGER 259 #define REAL 260 #define STRING 261 -#define HEX_STRING 262 -#define IDENTIFIER 263 +#define IDENTIFIER 262 +#define HEX_STRING 263 #define KEYWORD 264 #define KW_DCLASS 265 #define KW_STRUCT 266 @@ -122,15 +129,11 @@ #define START_PARAMETER_VALUE 296 #define START_PARAMETER_DESCRIPTION 297 +/* Value type. */ - -#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED - -# define yystype YYSTYPE /* obsolescent; will be withdrawn */ -# define YYSTYPE_IS_DECLARED 1 -#endif - extern YYSTYPE dcyylval; +int dcyyparse (void); +#endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED */ diff --git a/direct/src/dcparser/dcParser.yxx b/direct/src/dcparser/dcParser.yxx index 0a5c3d6aa0..b283c3314b 100644 --- a/direct/src/dcparser/dcParser.yxx +++ b/direct/src/dcparser/dcParser.yxx @@ -57,7 +57,7 @@ dc_init_parser(istream &in, const string &filename, DCFile &file) { } void -dc_init_parser_parameter_value(istream &in, const string &filename, +dc_init_parser_parameter_value(istream &in, const string &filename, DCPacker &packer) { dc_file = nullptr; current_packer = &packer; @@ -89,15 +89,16 @@ dc_cleanup_parser() { %token UNSIGNED_INTEGER %token SIGNED_INTEGER %token REAL -%token STRING HEX_STRING IDENTIFIER +%token STRING IDENTIFIER +%token HEX_STRING %token KEYWORD -%token KW_DCLASS -%token KW_STRUCT -%token KW_FROM -%token KW_IMPORT -%token KW_TYPEDEF -%token KW_KEYWORD +%token KW_DCLASS +%token KW_STRUCT +%token KW_FROM +%token KW_IMPORT +%token KW_TYPEDEF +%token KW_KEYWORD %token KW_SWITCH %token KW_CASE %token KW_DEFAULT @@ -224,7 +225,7 @@ import: { dc_file->add_import_module($2); } - | KW_FROM import_identifier KW_IMPORT + | KW_FROM import_identifier KW_IMPORT { dc_file->add_import_module($2); } @@ -255,7 +256,7 @@ typedef_decl: { if ($2 != nullptr) { DCTypedef *dtypedef = new DCTypedef($2); - + if (!dc_file->add_typedef(dtypedef)) { DCTypedef *old_typedef = dc_file->get_typedef_by_name(dtypedef->get_name()); if (old_typedef->is_bogus_typedef()) { @@ -294,7 +295,7 @@ dclass_or_struct: ; dclass: - KW_DCLASS optional_name + KW_DCLASS optional_name { current_class = new DCClass(dc_file, $2, false, false); } @@ -322,7 +323,7 @@ dclass_name: if (dclass->is_struct()) { yyerror("struct name not allowed"); } - + $$ = dclass; } } @@ -398,7 +399,7 @@ dclass_field: ; struct: - KW_STRUCT optional_name + KW_STRUCT optional_name { current_class = new DCClass(dc_file, $2, true, false); } @@ -426,7 +427,7 @@ struct_name: if (!dstruct->is_struct()) { yyerror("struct name required"); } - + $$ = dstruct; } } @@ -538,7 +539,7 @@ unnamed_parameter: named_parameter_with_default: named_parameter - | named_parameter '=' + | named_parameter '=' { current_packer = &default_packer; current_packer->clear_data(); @@ -553,7 +554,7 @@ named_parameter_with_default: is_valid = $1->is_valid(); } if (current_packer->end_pack()) { - $1->set_default_value(current_packer->get_string()); + $1->set_default_value(current_packer->get_bytes()); } else { if (is_valid) { @@ -568,7 +569,7 @@ named_parameter_with_default: unnamed_parameter_with_default: unnamed_parameter - | unnamed_parameter '=' + | unnamed_parameter '=' { current_packer = &default_packer; current_packer->clear_data(); @@ -583,7 +584,7 @@ unnamed_parameter_with_default: is_valid = $1->is_valid(); } if (current_packer->end_pack()) { - $1->set_default_value(current_packer->get_string()); + $1->set_default_value(current_packer->get_bytes()); } else { if (is_valid) { @@ -663,7 +664,7 @@ simple_type_name: $$ = simple_param; } | simple_type_name '%' number -{ +{ DCSimpleParameter *simple_param = $1->as_simple_parameter(); nassertr(simple_param != nullptr, 0); if (!simple_param->is_numeric_type()) { @@ -703,10 +704,10 @@ type_name: dtypedef = new DCTypedef($1); } } - + dc_file->add_typedef(dtypedef); } - + $$ = dtypedef->make_new_parameter(); } } @@ -974,7 +975,7 @@ parameter_value: { if ($1 != current_packer->get_current_field_name()) { ostringstream strm; - strm << "Got '" << $1 << "', expected '" + strm << "Got '" << $1 << "', expected '" << current_packer->get_current_field_name() << "'"; yyerror(strm.str()); } @@ -1005,7 +1006,7 @@ parameter_actual_value: { current_packer->pack_literal_value($1); } - | '{' + | '{' { current_packer->push(); } @@ -1013,7 +1014,7 @@ parameter_actual_value: { current_packer->pop(); } - | '[' + | '[' { current_packer->push(); } @@ -1021,7 +1022,7 @@ parameter_actual_value: { current_packer->pop(); } - | '(' + | '(' { current_packer->push(); } @@ -1223,7 +1224,7 @@ molecular_atom_list: if ($3 != nullptr) { current_molecular->add_atomic($3); if (!$3->is_bogus_field() && !current_molecular->compare_keywords(*$3)) { - yyerror("Mismatched keywords in molecule between " + + yyerror("Mismatched keywords in molecule between " + current_molecular->get_atomic(0)->get_name() + " and " + $3->get_name()); } @@ -1283,7 +1284,7 @@ switch_case: yyerror("Invalid value for switch parameter"); current_switch->add_invalid_case(); } else { - int case_index = current_switch->add_case(current_packer->get_string()); + int case_index = current_switch->add_case(current_packer->get_bytes()); if (case_index == -1) { yyerror("Duplicate case value"); } diff --git a/direct/src/dcparser/dcParserDefs.h b/direct/src/dcparser/dcParserDefs.h index 28659a37e3..f6f7aacd71 100644 --- a/direct/src/dcparser/dcParserDefs.h +++ b/direct/src/dcparser/dcParserDefs.h @@ -16,6 +16,7 @@ #include "dcbase.h" #include "dcSubatomicType.h" +#include "vector_uchar.h" class DCFile; class DCClass; @@ -61,6 +62,7 @@ public: const DCKeyword *keyword; } u; std::string str; + vector_uchar bytes; }; // The yacc-generated code expects to use the symbol 'YYSTYPE' to refer to the diff --git a/direct/src/dcparser/dcSimpleParameter.cxx b/direct/src/dcparser/dcSimpleParameter.cxx index bd2cb05534..5043307a4f 100644 --- a/direct/src/dcparser/dcSimpleParameter.cxx +++ b/direct/src/dcparser/dcSimpleParameter.cxx @@ -1069,6 +1069,52 @@ pack_string(DCPackData &pack_data, const string &value, } } +/** + * Packs the indicated numeric or string value into the stream. + */ +void DCSimpleParameter:: +pack_blob(DCPackData &pack_data, const vector_uchar &value, + bool &pack_error, bool &range_error) const { + size_t blob_size = value.size(); + + switch (_type) { + case ST_char: + case ST_uint8: + case ST_int8: + if (blob_size == 0) { + pack_error = true; + } else { + if (blob_size != 1) { + range_error = true; + } + _uint_range.validate((unsigned int)value[0], range_error); + do_pack_uint8(pack_data.get_write_pointer(1), (unsigned int)value[0]); + } + break; + + case ST_string: + case ST_blob: + _uint_range.validate(blob_size, range_error); + validate_uint_limits(blob_size, 16, range_error); + if (_num_length_bytes != 0) { + do_pack_uint16(pack_data.get_write_pointer(2), blob_size); + } + pack_data.append_data((const char *)value.data(), blob_size); + break; + + case ST_blob32: + _uint_range.validate(blob_size, range_error); + if (_num_length_bytes != 0) { + do_pack_uint32(pack_data.get_write_pointer(4), blob_size); + } + pack_data.append_data((const char *)value.data(), blob_size); + break; + + default: + pack_error = true; + } +} + /** * Packs the simpleParameter's specified default value (or a sensible default * if no value is specified) into the stream. Returns true if the default @@ -1937,6 +1983,79 @@ unpack_string(const char *data, size_t length, size_t &p, string &value, return; } +/** + * Unpacks the current numeric or string value from the stream. + */ +void DCSimpleParameter:: +unpack_blob(const char *data, size_t length, size_t &p, vector_uchar &value, + bool &pack_error, bool &range_error) const { + // If the type is a single byte, unpack it into a string of length 1. + switch (_type) { + case ST_char: + case ST_int8: + case ST_uint8: + { + if (p + 1 > length) { + pack_error = true; + return; + } + unsigned int int_value = do_unpack_uint8(data + p); + _uint_range.validate(int_value, range_error); + value.resize(1); + value[0] = int_value; + p++; + } + return; + + default: + break; + } + + size_t blob_size; + + if (_num_length_bytes == 0) { + blob_size = _fixed_byte_size; + + } else { + switch (_type) { + case ST_string: + case ST_blob: + if (p + 2 > length) { + pack_error = true; + return; + } + blob_size = do_unpack_uint16(data + p); + p += 2; + break; + + case ST_blob32: + if (p + 4 > length) { + pack_error = true; + return; + } + blob_size = do_unpack_uint32(data + p); + p += 4; + break; + + default: + pack_error = true; + return; + } + } + + _uint_range.validate(blob_size, range_error); + + if (p + blob_size > length) { + pack_error = true; + return; + } + value = vector_uchar((const unsigned char *)data + p, + (const unsigned char *)data + p + blob_size); + p += blob_size; + + return; +} + /** * Internally unpacks the current numeric or string value and validates it * against the type range limits, but does not return the value. Returns true diff --git a/direct/src/dcparser/dcSimpleParameter.h b/direct/src/dcparser/dcSimpleParameter.h index 7b47773a3d..cafd246d62 100644 --- a/direct/src/dcparser/dcSimpleParameter.h +++ b/direct/src/dcparser/dcSimpleParameter.h @@ -62,6 +62,8 @@ public: bool &pack_error, bool &range_error) const; virtual void pack_string(DCPackData &pack_data, const std::string &value, bool &pack_error, bool &range_error) const; + virtual void pack_blob(DCPackData &pack_data, const vector_uchar &value, + bool &pack_error, bool &range_error) const; virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const; virtual void unpack_double(const char *data, size_t length, size_t &p, @@ -76,6 +78,8 @@ public: uint64_t &value, bool &pack_error, bool &range_error) const; virtual void unpack_string(const char *data, size_t length, size_t &p, std::string &value, bool &pack_error, bool &range_error) const; + virtual void unpack_blob(const char *data, size_t length, size_t &p, + vector_uchar &value, bool &pack_error, bool &range_error) const; virtual bool unpack_validate(const char *data, size_t length, size_t &p, bool &pack_error, bool &range_error) const; virtual bool unpack_skip(const char *data, size_t length, size_t &p, diff --git a/direct/src/dcparser/dcSwitch.cxx b/direct/src/dcparser/dcSwitch.cxx index 5b23505209..041533efbe 100644 --- a/direct/src/dcparser/dcSwitch.cxx +++ b/direct/src/dcparser/dcSwitch.cxx @@ -109,7 +109,7 @@ get_num_cases() const { * if no case has this value. */ int DCSwitch:: -get_case_by_value(const string &case_value) const { +get_case_by_value(const vector_uchar &case_value) const { CasesByValue::const_iterator vi; vi = _cases_by_value.find(case_value); if (vi != _cases_by_value.end()) { @@ -140,9 +140,9 @@ get_default_case() const { /** * Returns the packed value associated with the indicated case. */ -string DCSwitch:: +vector_uchar DCSwitch:: get_value(int case_index) const { - nassertr(case_index >= 0 && case_index < (int)_cases.size(), string()); + nassertr(case_index >= 0 && case_index < (int)_cases.size(), vector_uchar()); return _cases[case_index]->_value; } @@ -198,7 +198,7 @@ is_field_valid() const { * -1. This is normally called only by the parser. */ int DCSwitch:: -add_case(const string &value) { +add_case(const vector_uchar &value) { int case_index = (int)_cases.size(); if (!_cases_by_value.insert(CasesByValue::value_type(value, case_index)).second) { add_invalid_case(); @@ -283,7 +283,8 @@ add_break() { const DCPackerInterface *DCSwitch:: apply_switch(const char *value_data, size_t length) const { CasesByValue::const_iterator vi; - vi = _cases_by_value.find(string(value_data, length)); + vi = _cases_by_value.find(vector_uchar((const unsigned char *)value_data, + (const unsigned char *)value_data + length)); if (vi != _cases_by_value.end()) { return _cases[(*vi).second]->_fields; } @@ -421,7 +422,7 @@ generate_hash(HashGenerator &hashgen) const { Cases::const_iterator ci; for (ci = _cases.begin(); ci != _cases.end(); ++ci) { const SwitchCase *dcase = (*ci); - hashgen.add_string(dcase->_value); + hashgen.add_blob(dcase->_value); const SwitchFields *fields = dcase->_fields; hashgen.add_int(fields->_fields.size()); @@ -702,7 +703,7 @@ do_check_match(const DCPackerInterface *) const { * */ DCSwitch::SwitchCase:: -SwitchCase(const string &value, DCSwitch::SwitchFields *fields) : +SwitchCase(const vector_uchar &value, DCSwitch::SwitchFields *fields) : _value(value), _fields(fields) { diff --git a/direct/src/dcparser/dcSwitch.h b/direct/src/dcparser/dcSwitch.h index ac58f5439d..31e69b452b 100644 --- a/direct/src/dcparser/dcSwitch.h +++ b/direct/src/dcparser/dcSwitch.h @@ -40,18 +40,18 @@ PUBLISHED: DCField *get_key_parameter() const; int get_num_cases() const; - int get_case_by_value(const std::string &case_value) const; + int get_case_by_value(const vector_uchar &case_value) const; DCPackerInterface *get_case(int n) const; DCPackerInterface *get_default_case() const; - std::string get_value(int case_index) const; + vector_uchar get_value(int case_index) const; int get_num_fields(int case_index) const; DCField *get_field(int case_index, int n) const; DCField *get_field_by_name(int case_index, const std::string &name) const; public: bool is_field_valid() const; - int add_case(const std::string &value); + int add_case(const vector_uchar &value); void add_invalid_case(); bool add_default(); bool add_field(DCField *field); @@ -98,13 +98,13 @@ public: class SwitchCase { public: - SwitchCase(const std::string &value, SwitchFields *fields); + SwitchCase(const vector_uchar &value, SwitchFields *fields); ~SwitchCase(); bool do_check_match_switch_case(const SwitchCase *other) const; public: - std::string _value; + vector_uchar _value; SwitchFields *_fields; }; @@ -137,7 +137,7 @@ private: bool _fields_added; // This map indexes into the _cases vector, above. - typedef pmap CasesByValue; + typedef pmap CasesByValue; CasesByValue _cases_by_value; }; diff --git a/direct/src/dcparser/hashGenerator.cxx b/direct/src/dcparser/hashGenerator.cxx index 900fd4bf42..bd3a6553b4 100644 --- a/direct/src/dcparser/hashGenerator.cxx +++ b/direct/src/dcparser/hashGenerator.cxx @@ -56,6 +56,18 @@ add_string(const std::string &str) { } } +/** + * Adds a blob to the hash, by breaking it down into a sequence of integers. + */ +void HashGenerator:: +add_blob(const vector_uchar &bytes) { + add_int(bytes.size()); + vector_uchar::const_iterator bi; + for (bi = bytes.begin(); bi != bytes.end(); ++bi) { + add_int(*bi); + } +} + /** * Returns the hash number generated. */ diff --git a/direct/src/dcparser/hashGenerator.h b/direct/src/dcparser/hashGenerator.h index 29ae1a6609..472e858cb0 100644 --- a/direct/src/dcparser/hashGenerator.h +++ b/direct/src/dcparser/hashGenerator.h @@ -16,6 +16,7 @@ #include "dcbase.h" #include "primeNumberGenerator.h" +#include "vector_uchar.h" /** * This class generates an arbitrary hash number from a sequence of ints. @@ -26,6 +27,7 @@ public: void add_int(int num); void add_string(const std::string &str); + void add_blob(const vector_uchar &bytes); unsigned long get_hash() const; diff --git a/direct/src/distributed/cConnectionRepository.cxx b/direct/src/distributed/cConnectionRepository.cxx index 6e429bfbec..16aea6e8dd 100644 --- a/direct/src/distributed/cConnectionRepository.cxx +++ b/direct/src/distributed/cConnectionRepository.cxx @@ -891,7 +891,7 @@ describe_message(std::ostream &out, const string &prefix, const Datagram &dg) const { DCPacker packer; - packer.set_unpack_data(dg.get_message()); + packer.set_unpack_data((const char *)dg.get_data(), dg.get_length(), false); CHANNEL_TYPE do_id; int msg_type; bool is_update = false; From 92539a310790b95c95fc239e8edba1a64185902e Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 19 Dec 2018 13:14:07 +0100 Subject: [PATCH 347/424] dcparser: rebuild dcParser.{cxx,h}.prebuilt --- direct/src/dcparser/dcParser.cxx.prebuilt | 555 +++++++++++----------- direct/src/dcparser/dcParser.h.prebuilt | 4 +- 2 files changed, 280 insertions(+), 279 deletions(-) diff --git a/direct/src/dcparser/dcParser.cxx.prebuilt b/direct/src/dcparser/dcParser.cxx.prebuilt index 9adc570263..399e4af2f9 100644 --- a/direct/src/dcparser/dcParser.cxx.prebuilt +++ b/direct/src/dcparser/dcParser.cxx.prebuilt @@ -1,8 +1,8 @@ -/* A Bison parser, made by GNU Bison 3.0.4. */ +/* A Bison parser, made by GNU Bison 3.1. */ /* Bison implementation for Yacc-like parsers in C - Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. + Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,7 +44,7 @@ #define YYBISON 1 /* Bison version. */ -#define YYBISON_VERSION "3.0.4" +#define YYBISON_VERSION "3.1" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" @@ -303,13 +303,13 @@ typedef signed char yytype_int8; #ifdef YYTYPE_UINT16 typedef YYTYPE_UINT16 yytype_uint16; #else -typedef unsigned short int yytype_uint16; +typedef unsigned short yytype_uint16; #endif #ifdef YYTYPE_INT16 typedef YYTYPE_INT16 yytype_int16; #else -typedef short int yytype_int16; +typedef short yytype_int16; #endif #ifndef YYSIZE_T @@ -321,7 +321,7 @@ typedef short int yytype_int16; # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else -# define YYSIZE_T unsigned int +# define YYSIZE_T unsigned # endif #endif @@ -373,7 +373,7 @@ typedef short int yytype_int16; # define YYUSE(E) /* empty */ #endif -#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +#if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ /* Suppress an incorrect diagnostic about yylval being uninitialized. */ # define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ _Pragma ("GCC diagnostic push") \ @@ -541,7 +541,7 @@ union yyalloc #define YYMAXUTOK 297 #define YYTRANSLATE(YYX) \ - ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + ((unsigned) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM as returned by yylex, without out-of-bounds checking. */ @@ -1083,7 +1083,7 @@ do { \ static void yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule) { - unsigned long int yylno = yyrline[yyrule]; + unsigned long yylno = yyrline[yyrule]; int yynrhs = yyr2[yyrule]; int yyi; YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", @@ -1309,6 +1309,7 @@ yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, case N: \ yyformat = S; \ break + default: /* Avoid compiler warnings. */ YYCASE_(0, YY_("syntax error")); YYCASE_(1, YY_("syntax error, unexpected %s")); YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); @@ -1514,7 +1515,7 @@ yyparse (void) yyvsp = yyvs + yysize - 1; YYDPRINTF ((stderr, "Stack size increased to %lu\n", - (unsigned long int) yystacksize)); + (unsigned long) yystacksize)); if (yyss + yystacksize - 1 <= yyssp) YYABORT; @@ -1625,15 +1626,15 @@ yyreduce: switch (yyn) { case 4: -#line 177 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 177 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { parameter_description = (yyvsp[0].u.field); } -#line 1633 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1634 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 7: -#line 186 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 186 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!dc_file->add_class((yyvsp[0].u.dclass))) { DCClass *old_class = dc_file->get_class_by_name((yyvsp[0].u.dclass)->get_name()); @@ -1644,77 +1645,77 @@ yyreduce: } } } -#line 1648 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1649 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 8: -#line 197 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 197 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!dc_file->add_switch((yyvsp[0].u.dswitch))) { yyerror("Duplicate class name: " + (yyvsp[0].u.dswitch)->get_name()); } } -#line 1658 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1659 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 13: -#line 210 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 210 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.str) = (yyvsp[-2].str) + string("/") + (yyvsp[0].str); } -#line 1666 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1667 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 15: -#line 218 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 218 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.str) = (yyvsp[-2].str) + string(".") + (yyvsp[0].str); } -#line 1674 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1675 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 16: -#line 225 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 225 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { dc_file->add_import_module((yyvsp[0].str)); } -#line 1682 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1683 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 17: -#line 229 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 229 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { dc_file->add_import_module((yyvsp[-1].str)); } -#line 1690 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1691 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 20: -#line 238 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 238 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { dc_file->add_import_symbol("*"); } -#line 1698 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1699 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 21: -#line 245 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 245 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { dc_file->add_import_symbol((yyvsp[0].str)); } -#line 1706 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1707 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 22: -#line 249 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 249 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { dc_file->add_import_symbol((yyvsp[0].str)); } -#line 1714 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1715 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 23: -#line 256 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 256 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].u.parameter) != nullptr) { DCTypedef *dtypedef = new DCTypedef((yyvsp[0].u.parameter)); @@ -1729,19 +1730,19 @@ yyreduce: } } } -#line 1733 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1734 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 26: -#line 279 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 279 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { dc_file->add_keyword((yyvsp[0].str)); } -#line 1741 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1742 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 27: -#line 283 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 283 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { // This keyword has already been defined. But since we are now // explicitly defining it, clear its bitmask, so that we will have a @@ -1749,28 +1750,28 @@ yyreduce: // historical hash code support later. ((DCKeyword *)(yyvsp[0].u.keyword))->clear_historical_flag(); } -#line 1753 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1754 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 30: -#line 299 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 299 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_class = new DCClass(dc_file, (yyvsp[0].str), false, false); } -#line 1761 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1762 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 31: -#line 303 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 303 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.dclass) = current_class; current_class = (yyvsp[-4].u.dclass); } -#line 1770 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1771 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 32: -#line 311 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 311 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (dc_file == nullptr) { yyerror("No DCFile available, so no class names are predefined."); @@ -1790,21 +1791,21 @@ yyreduce: (yyval.u.dclass) = dclass; } } -#line 1794 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1795 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 35: -#line 339 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 339 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].u.dclass) != nullptr) { current_class->add_parent((yyvsp[0].u.dclass)); } } -#line 1804 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1805 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 36: -#line 345 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 345 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!dc_multiple_inheritance) { yyerror("Multiple inheritance is not supported without \"dc-multiple-inheritance 1\" in your Config.prc file."); @@ -1815,11 +1816,11 @@ yyreduce: } } } -#line 1819 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1820 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 39: -#line 361 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 361 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[-1].u.field) == nullptr) { // Pass this error up. @@ -1829,11 +1830,11 @@ yyreduce: yyerror("A non-network field cannot be stored on a dclass"); } } -#line 1833 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1834 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 40: -#line 374 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 374 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[-1].u.field) != nullptr) { if ((yyvsp[-1].u.field)->get_name().empty()) { @@ -1843,11 +1844,11 @@ yyreduce: } (yyval.u.field) = (yyvsp[-1].u.field); } -#line 1847 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1848 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 42: -#line 385 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 385 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { yyerror("Unnamed parameters are not allowed on a dclass"); if ((yyvsp[-1].u.parameter) != nullptr) { @@ -1855,39 +1856,39 @@ yyreduce: } (yyval.u.field) = (yyvsp[-1].u.parameter); } -#line 1859 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1860 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 43: -#line 393 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 393 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[-1].u.parameter) != nullptr) { (yyvsp[-1].u.parameter)->copy_keywords(current_keyword_list); } (yyval.u.field) = (yyvsp[-1].u.parameter); } -#line 1870 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1871 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 44: -#line 403 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 403 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_class = new DCClass(dc_file, (yyvsp[0].str), true, false); } -#line 1878 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1879 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 45: -#line 407 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 407 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.dclass) = current_class; current_class = (yyvsp[-4].u.dclass); } -#line 1887 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1888 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 46: -#line 415 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 415 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (dc_file == nullptr) { yyerror("No DCFile available, so no struct names are predefined."); @@ -1907,31 +1908,31 @@ yyreduce: (yyval.u.dclass) = dstruct; } } -#line 1911 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1912 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 49: -#line 443 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 443 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].u.dclass) != nullptr) { current_class->add_parent((yyvsp[0].u.dclass)); } } -#line 1921 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1922 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 50: -#line 449 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 449 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].u.dclass) != nullptr) { current_class->add_parent((yyvsp[0].u.dclass)); } } -#line 1931 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1932 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 53: -#line 460 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 460 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[-1].u.field) == nullptr) { // Pass this error up. @@ -1939,38 +1940,38 @@ yyreduce: yyerror("Duplicate field name: " + (yyvsp[-1].u.field)->get_name()); } } -#line 1943 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1944 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 54: -#line 471 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 471 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[-1].u.field)->get_name().empty()) { yyerror("Field name required."); } (yyval.u.field) = (yyvsp[-1].u.field); } -#line 1954 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1955 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 56: -#line 479 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 479 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = (yyvsp[-1].u.parameter); } -#line 1962 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1963 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 57: -#line 483 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 483 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = (yyvsp[-1].u.parameter); } -#line 1970 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1971 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 58: -#line 490 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 490 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (current_class == nullptr) { yyerror("Cannot define a method outside of a struct or class."); @@ -1980,46 +1981,46 @@ yyreduce: current_atomic = new DCAtomicField((yyvsp[-1].str), current_class, false); } } -#line 1984 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1985 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 59: -#line 500 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 500 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = current_atomic; current_atomic = (yyvsp[-2].u.atomic); } -#line 1993 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 1994 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 64: -#line 518 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 518 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].u.parameter) != nullptr) { current_atomic->add_element((yyvsp[0].u.parameter)); } } -#line 2003 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2004 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 65: -#line 527 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 527 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_parameter = (yyvsp[0].u.parameter); } -#line 2011 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2012 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 66: -#line 531 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 531 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.parameter) = (yyvsp[0].u.parameter); } -#line 2019 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2020 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 69: -#line 543 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 543 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer = &default_packer; current_packer->clear_data(); @@ -2027,11 +2028,11 @@ yyreduce: current_packer->begin_pack((yyvsp[-1].u.parameter)); } } -#line 2031 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2032 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 70: -#line 551 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 551 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { bool is_valid = false; if ((yyvsp[-3].u.parameter) != nullptr) { @@ -2049,11 +2050,11 @@ yyreduce: // just ignore the default value in this case. } } -#line 2053 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2054 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 72: -#line 573 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 573 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer = &default_packer; current_packer->clear_data(); @@ -2061,11 +2062,11 @@ yyreduce: current_packer->begin_pack((yyvsp[-1].u.parameter)); } } -#line 2065 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2066 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 73: -#line 581 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 581 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { bool is_valid = false; if ((yyvsp[-3].u.parameter) != nullptr) { @@ -2083,59 +2084,59 @@ yyreduce: // just ignore the default value in this case. } } -#line 2087 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2088 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 78: -#line 612 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 612 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = (yyvsp[0].u.parameter); } -#line 2095 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2096 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 79: -#line 616 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 616 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = (yyvsp[0].u.field); } -#line 2103 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2104 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 80: -#line 623 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 623 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = (yyvsp[-1].u.field); } -#line 2111 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2112 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 81: -#line 627 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 627 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = (yyvsp[-1].u.parameter); } -#line 2119 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2120 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 82: -#line 631 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 631 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = (yyvsp[-1].u.parameter); } -#line 2127 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2128 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 83: -#line 638 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 638 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.parameter) = new DCSimpleParameter((yyvsp[0].u.subatomic)); } -#line 2135 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2136 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 84: -#line 642 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 642 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { DCSimpleParameter *simple_param = (yyvsp[-3].u.parameter)->as_simple_parameter(); nassertr(simple_param != nullptr, 0); @@ -2144,11 +2145,11 @@ yyreduce: } (yyval.u.parameter) = simple_param; } -#line 2148 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2149 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 85: -#line 651 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 651 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { DCSimpleParameter *simple_param = (yyvsp[-2].u.parameter)->as_simple_parameter(); nassertr(simple_param != nullptr, 0); @@ -2164,11 +2165,11 @@ yyreduce: } (yyval.u.parameter) = simple_param; } -#line 2168 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2169 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 86: -#line 667 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 667 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { DCSimpleParameter *simple_param = (yyvsp[-2].u.parameter)->as_simple_parameter(); nassertr(simple_param != nullptr, 0); @@ -2180,11 +2181,11 @@ yyreduce: } (yyval.u.parameter) = simple_param; } -#line 2184 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2185 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 88: -#line 683 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 683 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (dc_file == nullptr) { yyerror("Invalid type."); @@ -2216,11 +2217,11 @@ yyreduce: (yyval.u.parameter) = dtypedef->make_new_parameter(); } } -#line 2220 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2221 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 89: -#line 715 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 715 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { // This is an inline struct definition. if ((yyvsp[0].u.dclass) == nullptr) { @@ -2236,11 +2237,11 @@ yyreduce: (yyval.u.parameter) = new DCClassParameter((yyvsp[0].u.dclass)); } } -#line 2240 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2241 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 90: -#line 731 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 731 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { // This is an inline switch definition. if ((yyvsp[0].u.dswitch) == nullptr) { @@ -2256,41 +2257,41 @@ yyreduce: (yyval.u.parameter) = new DCSwitchParameter((yyvsp[0].u.dswitch)); } } -#line 2260 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2261 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 91: -#line 750 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 750 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { double_range.clear(); } -#line 2268 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2269 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 92: -#line 754 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 754 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { double_range.clear(); if (!double_range.add_range((yyvsp[0].u.real), (yyvsp[0].u.real))) { yyerror("Overlapping range"); } } -#line 2279 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2280 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 93: -#line 761 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 761 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { double_range.clear(); if (!double_range.add_range((yyvsp[-2].u.real), (yyvsp[0].u.real))) { yyerror("Overlapping range"); } } -#line 2290 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2291 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 94: -#line 768 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 768 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { double_range.clear(); if ((yyvsp[0].u.real) >= 0) { @@ -2299,31 +2300,31 @@ yyreduce: yyerror("Overlapping range"); } } -#line 2303 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2304 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 95: -#line 777 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 777 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!double_range.add_range((yyvsp[0].u.real), (yyvsp[0].u.real))) { yyerror("Overlapping range"); } } -#line 2313 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2314 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 96: -#line 783 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 783 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!double_range.add_range((yyvsp[-2].u.real), (yyvsp[0].u.real))) { yyerror("Overlapping range"); } } -#line 2323 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2324 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 97: -#line 789 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 789 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].u.real) >= 0) { yyerror("Syntax error"); @@ -2331,82 +2332,82 @@ yyreduce: yyerror("Overlapping range"); } } -#line 2335 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2336 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 98: -#line 800 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 800 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { uint_range.clear(); } -#line 2343 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2344 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 99: -#line 804 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 804 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { uint_range.clear(); if (!uint_range.add_range((yyvsp[0].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } -#line 2354 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2355 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 100: -#line 811 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 811 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { uint_range.clear(); if (!uint_range.add_range((yyvsp[-2].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } -#line 2365 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2366 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 101: -#line 818 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 818 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { uint_range.clear(); if (!uint_range.add_range((yyvsp[-1].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } -#line 2376 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2377 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 102: -#line 825 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 825 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!uint_range.add_range((yyvsp[0].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } -#line 2386 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2387 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 103: -#line 831 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 831 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!uint_range.add_range((yyvsp[-2].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } -#line 2396 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2397 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 104: -#line 837 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 837 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!uint_range.add_range((yyvsp[-1].u.s_uint), (yyvsp[0].u.s_uint))) { yyerror("Overlapping range"); } } -#line 2406 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2407 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 106: -#line 847 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 847 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[-3].u.parameter) == nullptr) { (yyval.u.parameter) = nullptr; @@ -2414,20 +2415,20 @@ yyreduce: (yyval.u.parameter) = (yyvsp[-3].u.parameter)->append_array_specification(uint_range); } } -#line 2418 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2419 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 107: -#line 858 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 858 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_parameter->set_name((yyvsp[0].str)); (yyval.u.parameter) = current_parameter; } -#line 2427 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2428 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 108: -#line 863 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 863 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { DCSimpleParameter *simple_param = (yyvsp[-2].u.parameter)->as_simple_parameter(); if (simple_param == nullptr || simple_param->get_typedef() != nullptr) { @@ -2442,11 +2443,11 @@ yyreduce: } } } -#line 2446 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2447 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 109: -#line 878 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 878 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { DCSimpleParameter *simple_param = (yyvsp[-2].u.parameter)->as_simple_parameter(); if (simple_param == nullptr || simple_param->get_typedef() != nullptr) { @@ -2461,19 +2462,19 @@ yyreduce: } } } -#line 2465 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2466 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 110: -#line 893 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 893 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.parameter) = (yyvsp[-3].u.parameter)->append_array_specification(uint_range); } -#line 2473 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2474 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 111: -#line 900 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 900 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].str).length() != 1) { yyerror("Single character required."); @@ -2482,11 +2483,11 @@ yyreduce: (yyval.u.s_uint) = (unsigned char)(yyvsp[0].str)[0]; } } -#line 2486 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2487 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 113: -#line 913 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 913 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.s_uint) = (unsigned int)(yyvsp[0].u.uint64); if ((yyval.u.s_uint) != (yyvsp[0].u.uint64)) { @@ -2494,11 +2495,11 @@ yyreduce: (yyval.u.s_uint) = 1; } } -#line 2498 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2499 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 114: -#line 924 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 924 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.s_uint) = (unsigned int)-(yyvsp[0].u.int64); if ((yyvsp[0].u.int64) >= 0) { @@ -2509,27 +2510,27 @@ yyreduce: (yyval.u.s_uint) = 1; } } -#line 2513 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2514 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 117: -#line 946 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 946 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.real) = (double)(yyvsp[0].u.uint64); } -#line 2521 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2522 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 118: -#line 950 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 950 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.real) = (double)(yyvsp[0].u.int64); } -#line 2529 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2530 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 120: -#line 958 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 958 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].str).length() != 1) { yyerror("Single character required."); @@ -2538,18 +2539,18 @@ yyreduce: (yyval.u.real) = (double)(unsigned char)(yyvsp[0].str)[0]; } } -#line 2542 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2543 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 122: -#line 972 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 972 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { } -#line 2549 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2550 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 123: -#line 975 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 975 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[-1].str) != current_packer->get_current_field_name()) { ostringstream strm; @@ -2558,348 +2559,348 @@ yyreduce: yyerror(strm.str()); } } -#line 2562 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2563 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 124: -#line 984 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 984 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { } -#line 2569 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2570 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 125: -#line 990 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 990 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->pack_int64((yyvsp[0].u.int64)); } -#line 2577 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2578 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 126: -#line 994 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 994 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->pack_uint64((yyvsp[0].u.uint64)); } -#line 2585 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2586 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 127: -#line 998 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 998 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->pack_double((yyvsp[0].u.real)); } -#line 2593 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2594 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 128: -#line 1002 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1002 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->pack_string((yyvsp[0].str)); } -#line 2601 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2602 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 129: -#line 1006 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1006 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->pack_literal_value((yyvsp[0].bytes)); } -#line 2609 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2610 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 130: -#line 1010 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1010 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->push(); } -#line 2617 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2618 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 131: -#line 1014 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1014 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->pop(); } -#line 2625 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2626 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 132: -#line 1018 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1018 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->push(); } -#line 2633 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2634 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 133: -#line 1022 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1022 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->pop(); } -#line 2641 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2642 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 134: -#line 1026 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1026 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->push(); } -#line 2649 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2650 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 135: -#line 1030 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1030 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer->pop(); } -#line 2657 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2658 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 136: -#line 1034 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1034 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { for (unsigned int i = 0; i < (yyvsp[0].u.s_uint); i++) { current_packer->pack_int64((yyvsp[-2].u.int64)); } } -#line 2667 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2668 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 137: -#line 1040 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1040 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { for (unsigned int i = 0; i < (yyvsp[0].u.s_uint); i++) { current_packer->pack_uint64((yyvsp[-2].u.uint64)); } } -#line 2677 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2678 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 138: -#line 1046 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1046 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { for (unsigned int i = 0; i < (yyvsp[0].u.s_uint); i++) { current_packer->pack_double((yyvsp[-2].u.real)); } } -#line 2687 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2688 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 139: -#line 1052 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1052 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { for (unsigned int i = 0; i < (yyvsp[0].u.s_uint); i++) { current_packer->pack_literal_value((yyvsp[-2].bytes)); } } -#line 2697 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2698 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 146: -#line 1076 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1076 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_int8; } -#line 2705 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2706 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 147: -#line 1080 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1080 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_int16; } -#line 2713 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2714 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 148: -#line 1084 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1084 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_int32; } -#line 2721 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2722 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 149: -#line 1088 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1088 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_int64; } -#line 2729 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2730 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 150: -#line 1092 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1092 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_uint8; } -#line 2737 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2738 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 151: -#line 1096 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1096 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_uint16; } -#line 2745 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2746 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 152: -#line 1100 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1100 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_uint32; } -#line 2753 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2754 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 153: -#line 1104 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1104 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_uint64; } -#line 2761 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2762 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 154: -#line 1108 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1108 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_float64; } -#line 2769 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2770 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 155: -#line 1112 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1112 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_string; } -#line 2777 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2778 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 156: -#line 1116 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1116 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_blob; } -#line 2785 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2786 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 157: -#line 1120 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1120 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_blob32; } -#line 2793 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2794 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 158: -#line 1124 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1124 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_int8array; } -#line 2801 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2802 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 159: -#line 1128 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1128 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_int16array; } -#line 2809 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2810 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 160: -#line 1132 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1132 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_int32array; } -#line 2817 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2818 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 161: -#line 1136 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1136 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_uint8array; } -#line 2825 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2826 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 162: -#line 1140 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1140 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_uint16array; } -#line 2833 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2834 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 163: -#line 1144 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1144 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_uint32array; } -#line 2841 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2842 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 164: -#line 1148 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1148 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_uint32uint8array; } -#line 2849 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2850 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 165: -#line 1152 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1152 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.subatomic) = ST_char; } -#line 2857 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2858 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 166: -#line 1159 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1159 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_keyword_list.clear_keywords(); } -#line 2865 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2866 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 167: -#line 1163 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1163 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_keyword_list.add_keyword((yyvsp[0].u.keyword)); } -#line 2873 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2874 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 168: -#line 1170 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1170 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (current_keyword_list.get_num_keywords() != 0) { yyerror("Communication keywords are not allowed here."); } } -#line 2883 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2884 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 169: -#line 1179 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1179 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_molecular = new DCMolecularField((yyvsp[-1].str), current_class); } -#line 2891 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2892 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 170: -#line 1183 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1183 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = current_molecular; } -#line 2899 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2900 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 171: -#line 1190 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1190 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { DCField *field = current_class->get_field_by_name((yyvsp[0].str)); (yyval.u.atomic) = nullptr; @@ -2923,21 +2924,21 @@ yyreduce: } } } -#line 2927 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2928 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 172: -#line 1217 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1217 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].u.atomic) != nullptr) { current_molecular->add_atomic((yyvsp[0].u.atomic)); } } -#line 2937 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2938 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 173: -#line 1223 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1223 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if ((yyvsp[0].u.atomic) != nullptr) { current_molecular->add_atomic((yyvsp[0].u.atomic)); @@ -2948,36 +2949,36 @@ yyreduce: } } } -#line 2952 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2953 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 174: -#line 1237 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1237 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.str) = ""; } -#line 2960 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2961 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 176: -#line 1245 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1245 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_switch = new DCSwitch((yyvsp[-4].str), (yyvsp[-2].u.field)); } -#line 2968 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2969 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 177: -#line 1249 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1249 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.dswitch) = current_switch; current_switch = (DCSwitch *)(yyvsp[-2].u.parameter); } -#line 2977 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2978 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 183: -#line 1263 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1263 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!current_switch->is_field_valid()) { yyerror("case declaration required before first element"); @@ -2987,21 +2988,21 @@ yyreduce: } } } -#line 2991 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 2992 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 184: -#line 1276 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1276 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_packer = &default_packer; current_packer->clear_data(); current_packer->begin_pack(current_switch->get_key_parameter()); } -#line 3001 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 3002 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 185: -#line 1282 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1282 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!current_packer->end_pack()) { yyerror("Invalid value for switch parameter"); @@ -3013,45 +3014,45 @@ yyreduce: } } } -#line 3017 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 3018 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 186: -#line 1297 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1297 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { if (!current_switch->add_default()) { yyerror("Default case already defined"); } } -#line 3027 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 3028 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 187: -#line 1306 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1306 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { current_switch->add_break(); } -#line 3035 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 3036 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 188: -#line 1313 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1313 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = (yyvsp[0].u.parameter); } -#line 3043 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 3044 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; case 189: -#line 1317 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1646 */ +#line 1317 "direct/src/dcparser/dcParser.yxx" /* yacc.c:1651 */ { (yyval.u.field) = (yyvsp[0].u.parameter); } -#line 3051 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 3052 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ break; -#line 3055 "built/tmp/dcParser.yxx.c" /* yacc.c:1646 */ +#line 3056 "built/tmp/dcParser.yxx.c" /* yacc.c:1651 */ default: break; } /* User semantic actions sometimes alter yychar, and that requires diff --git a/direct/src/dcparser/dcParser.h.prebuilt b/direct/src/dcparser/dcParser.h.prebuilt index 7b46c4c7b1..8880652bb0 100644 --- a/direct/src/dcparser/dcParser.h.prebuilt +++ b/direct/src/dcparser/dcParser.h.prebuilt @@ -1,8 +1,8 @@ -/* A Bison parser, made by GNU Bison 3.0.4. */ +/* A Bison parser, made by GNU Bison 3.1. */ /* Bison interface for Yacc-like parsers in C - Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. + Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From 5cafe8552b2cec6753fbef0cc576fde4972cac5d Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 19 Dec 2018 20:26:51 +0100 Subject: [PATCH 348/424] gobj: add F_rg, F_rg16, F_rg32, F_rgb32 to TexturePeeker --- panda/src/gobj/texturePeeker.cxx | 19 +++++++++++++++++++ panda/src/gobj/texturePeeker.h | 1 + 2 files changed, 20 insertions(+) diff --git a/panda/src/gobj/texturePeeker.cxx b/panda/src/gobj/texturePeeker.cxx index 7bb17ab87a..ece87f36a5 100644 --- a/panda/src/gobj/texturePeeker.cxx +++ b/panda/src/gobj/texturePeeker.cxx @@ -140,6 +140,12 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) { _get_texel = get_texel_la; break; + case Texture::F_rg16: + case Texture::F_rg32: + case Texture::F_rg: + _get_texel = get_texel_rg; + break; + case Texture::F_rgb: case Texture::F_rgb5: case Texture::F_rgb8: @@ -148,6 +154,7 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) { case Texture::F_rgb332: case Texture::F_r11_g11_b10: case Texture::F_rgb9_e5: + case Texture::F_rgb32: _get_texel = get_texel_rgb; break; @@ -582,6 +589,18 @@ get_texel_la(LColor &color, const unsigned char *&p, GetComponentFunc *get_compo color[3] = (*get_component)(p); } +/** + * Gets the color of the texel at byte p, given that the texture is in format + * F_rg or similar. + */ +void TexturePeeker:: +get_texel_rg(LColor &color, const unsigned char *&p, GetComponentFunc *get_component) { + color[0] = (*get_component)(p); + color[1] = (*get_component)(p); + color[2] = 0.0f; + color[3] = 1.0f; +} + /** * Gets the color of the texel at byte p, given that the texture is in format * F_rgb or similar. diff --git a/panda/src/gobj/texturePeeker.h b/panda/src/gobj/texturePeeker.h index ffe3aba2b1..e88603bcea 100644 --- a/panda/src/gobj/texturePeeker.h +++ b/panda/src/gobj/texturePeeker.h @@ -77,6 +77,7 @@ private: static void get_texel_a(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); static void get_texel_l(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); static void get_texel_la(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); + static void get_texel_rg(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); static void get_texel_rgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); static void get_texel_rgba(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); static void get_texel_srgb(LColor &color, const unsigned char *&p, GetComponentFunc *get_component); From ebef9f3de8d2eb1c3628a25fe3bd08aa59c8804d Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 19 Dec 2018 20:45:57 +0100 Subject: [PATCH 349/424] pgraph: add assertions to prevent invalid TransformState If this commit is causing crashes in your code, it is probably exposing a bug in some other system that is assigning bad TransformState objects. --- panda/src/pgraph/pandaNode.cxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/panda/src/pgraph/pandaNode.cxx b/panda/src/pgraph/pandaNode.cxx index 7d53cbcaf7..1e3d427e28 100644 --- a/panda/src/pgraph/pandaNode.cxx +++ b/panda/src/pgraph/pandaNode.cxx @@ -1084,6 +1084,8 @@ set_effects(const RenderEffects *effects, Thread *current_thread) { */ void PandaNode:: set_transform(const TransformState *transform, Thread *current_thread) { + nassertv(!transform->is_invalid()); + // Need to have this held before we grab any other locks. LightMutexHolder holder(_dirty_prev_transforms._lock); @@ -1120,6 +1122,8 @@ set_transform(const TransformState *transform, Thread *current_thread) { */ void PandaNode:: set_prev_transform(const TransformState *transform, Thread *current_thread) { + nassertv(!transform->is_invalid()); + // Need to have this held before we grab any other locks. LightMutexHolder holder(_dirty_prev_transforms._lock); @@ -3849,6 +3853,9 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) { // Mark the bounds stale. ++_next_update; + nassertr(!_transform->is_invalid(), pi); + nassertr(!_prev_transform->is_invalid(), pi); + return pi; } From daa3100bf194592fb8ac865dd1ab8015f4399651 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 19 Dec 2018 20:47:57 +0100 Subject: [PATCH 350/424] pgraph: mark TransformState as invalid in destructor This, in combination with ebef9f3de8d2eb1c3628a25fe3bd08aa59c8804d, makes it easier to catch issues in code that is accidentally letting get temporary TransformState objects get destroyed. It does not affect release builds. --- panda/src/pgraph/transformState.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/panda/src/pgraph/transformState.cxx b/panda/src/pgraph/transformState.cxx index 4d02425683..fc8b3de606 100644 --- a/panda/src/pgraph/transformState.cxx +++ b/panda/src/pgraph/transformState.cxx @@ -96,6 +96,10 @@ TransformState:: // true now, probably we've been double-deleted. nassertv(get_ref_count() == 0); _cache_stats.add_num_states(-1); + +#ifndef NDEBUG + _flags = F_is_invalid | F_is_destructing; +#endif } /** From cbda6166a8b6b4222ec37cb61f531e444b7f31e8 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 19 Dec 2018 20:53:11 +0100 Subject: [PATCH 351/424] tform: fix MouseWatcherRegion area calculation Fixes #233 --- panda/src/tform/mouseWatcherRegion.I | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/tform/mouseWatcherRegion.I b/panda/src/tform/mouseWatcherRegion.I index 50dfada487..91ccbc7063 100644 --- a/panda/src/tform/mouseWatcherRegion.I +++ b/panda/src/tform/mouseWatcherRegion.I @@ -17,11 +17,11 @@ INLINE MouseWatcherRegion:: MouseWatcherRegion(const std::string &name, PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top) : - Namable(name), - _frame(left, right, bottom, top) + Namable(name) { _sort = 0; _flags = F_active; + set_frame(left, right, bottom, top); } /** From 837d3d6b6dbbb1a168d9813561be60a729401e02 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 19 Dec 2018 20:54:48 +0100 Subject: [PATCH 352/424] tform: add properties to MouseWatcherRegion --- panda/src/tform/mouseWatcherRegion.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/panda/src/tform/mouseWatcherRegion.h b/panda/src/tform/mouseWatcherRegion.h index a49cfcbc6e..c3e668bf34 100644 --- a/panda/src/tform/mouseWatcherRegion.h +++ b/panda/src/tform/mouseWatcherRegion.h @@ -61,6 +61,15 @@ PUBLISHED: void output(std::ostream &out) const; void write(std::ostream &out, int indent_level = 0) const; +PUBLISHED: + MAKE_PROPERTY(frame, get_frame, set_frame); + MAKE_PROPERTY(area, get_area); + + MAKE_PROPERTY(sort, get_sort, set_sort); + MAKE_PROPERTY(active, get_active, set_active); + MAKE_PROPERTY(keyboard, get_keyboard, set_keyboard); + MAKE_PROPERTY(suppress_flags, get_suppress_flags, set_suppress_flags); + public: INLINE bool operator < (const MouseWatcherRegion &other) const; From 9035b4a6105fe383ab7091104f7a238c4594b70b Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 19 Dec 2018 20:56:42 +0100 Subject: [PATCH 353/424] pgraph: don't bother calling xform() on empty BoundingVolume --- panda/src/pgraph/pandaNode.cxx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/panda/src/pgraph/pandaNode.cxx b/panda/src/pgraph/pandaNode.cxx index 1e3d427e28..25a65e19ae 100644 --- a/panda/src/pgraph/pandaNode.cxx +++ b/panda/src/pgraph/pandaNode.cxx @@ -3514,12 +3514,12 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe const BoundingVolume **child_begin = &child_volumes[0]; const BoundingVolume **child_end = child_begin + child_volumes_i; ((BoundingVolume *)gbv)->around(child_begin, child_end); - } - // If we have a transform, apply it to the bounding volume we just - // computed. - if (!transform->is_identity()) { - gbv->xform(transform->get_mat()); + // If we have a transform, apply it to the bounding volume we just + // computed. + if (!transform->is_identity()) { + gbv->xform(transform->get_mat()); + } } cdataw->_external_bounds = gbv; From ec3c2ad25a1b141778ca4284b503cdfe5475aaa5 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 19 Dec 2018 18:52:09 -0800 Subject: [PATCH 354/424] deploy-ng: Add __init__.py to direct.dist --- direct/src/dist/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 direct/src/dist/__init__.py diff --git a/direct/src/dist/__init__.py b/direct/src/dist/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 88fc3d4ca399db42c1be20c198cd3257d8c8f754 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Sat, 22 Dec 2018 14:13:40 -0800 Subject: [PATCH 355/424] deploy-ng: Add --skip-build and --dist-dir options to bdist_apps dist-dir defaults to 'dist' like sdist and bdist_wheel, which makes bdist_apps more consistent with the rest of the Python ecosystem. --- direct/src/dist/commands.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index f35916d067..1ee2527950 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -226,7 +226,7 @@ class build_apps(setuptools.Command): self.default_prc_dir = None self.log_filename = None self.log_append = False - self.requirements_path = './requirements.txt' + self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt') self.use_optimized_wheels = True self.optimized_wheel_index = '' self.pypi_extra_indexes = [] @@ -1149,6 +1149,8 @@ class bdist_apps(setuptools.Command): description = 'bundle built Panda3D applications into distributable forms' user_options = build_apps.user_options + [ + ('dist-dir=', 'd', 'directory to put final built distributions in'), + ('skip-build', None, 'skip rebuilding everything (for testing/debugging)'), ] def _build_apps_options(self): @@ -1156,6 +1158,8 @@ class bdist_apps(setuptools.Command): def initialize_options(self): self.installers = {} + self.dist_dir = os.path.join(os.getcwd(), 'dist') + self.skip_build = False for opt in self._build_apps_options(): setattr(self, opt, None) @@ -1310,11 +1314,13 @@ class bdist_apps(setuptools.Command): if optval is not None: setattr(build_cmd, opt, optval) build_cmd.finalize_options() - self.run_command('build_apps') + if not self.skip_build: + self.run_command('build_apps') platforms = build_cmd.platforms build_base = build_cmd.build_base - os.chdir(build_base) + os.makedirs(self.dist_dir, exist_ok=True) + os.chdir(self.dist_dir) for platform in platforms: build_dir = os.path.join(build_base, platform) From 0668509791116a11df78b540674c8e6695586a13 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 15:07:02 +0100 Subject: [PATCH 356/424] stdpy: remove commented-out method that is confusing Doxygen --- direct/src/stdpy/thread.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/direct/src/stdpy/thread.py b/direct/src/stdpy/thread.py index c10675dd4c..29844b32e2 100644 --- a/direct/src/stdpy/thread.py +++ b/direct/src/stdpy/thread.py @@ -277,13 +277,6 @@ class _local(object): d = _get_thread_locals(core.Thread.getCurrentThread(), id(self)) d[key] = value -## def __getattr__(self, key): -## d = _get_thread_locals(core.Thread.getCurrentThread(), id(self)) -## try: -## return d[key] -## except KeyError: -## raise AttributeError - def __getattribute__(self, key): d = _get_thread_locals(core.Thread.getCurrentThread(), id(self)) if key == '__dict__': @@ -292,6 +285,3 @@ class _local(object): return d[key] except KeyError: return object.__getattribute__(self, key) - - - From a0a2bf59bb61f1de2d1ba6eb1dc95a34341df0d0 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 15:42:43 +0100 Subject: [PATCH 357/424] actor: remove commented-out code that is confusing doxygen --- direct/src/actor/Actor.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/direct/src/actor/Actor.py b/direct/src/actor/Actor.py index b9ba299aaa..b7d48f2d87 100644 --- a/direct/src/actor/Actor.py +++ b/direct/src/actor/Actor.py @@ -653,15 +653,11 @@ class Actor(DirectObject, NodePath): """ # make sure we don't call this twice in a row # and pollute the the switches dictionary -## sortedKeys = list(self.switches.keys()) -## sortedKeys.sort() child = self.__LODNode.find(str(lodName)) index = self.__LODNode.node().findChild(child.node()) self.__LODNode.node().forceSwitch(index) def printLOD(self): -## sortedKeys = list(self.switches.keys()) -## sortedKeys.sort() sortedKeys = self.__sortedLODNames for eachLod in sortedKeys: print("python switches for %s: in: %d, out %d" % (eachLod, @@ -679,12 +675,6 @@ class Actor(DirectObject, NodePath): """ Restore all switch distance info (usually after a useLOD call)""" self.__LODNode.node().clearForceSwitch() -## sortedKeys = list(self.switches.keys()) -## sortedKeys.sort() -## for eachLod in sortedKeys: -## index = sortedKeys.index(eachLod) -## self.__LODNode.node().setSwitch(index, self.switches[eachLod][0], -## self.switches[eachLod][1]) def addLOD(self, lodName, inDist=0, outDist=0, center=None): """addLOD(self, string) @@ -706,8 +696,6 @@ class Actor(DirectObject, NodePath): # save the switch distance info self.switches[lodName] = [inDist, outDist] # add the switch distance info -## sortedKeys = list(self.switches.keys()) -## sortedKeys.sort() self.__LODNode.node().setSwitch(self.getLODIndex(lodName), inDist, outDist) def getLODIndex(self, lodName): From 6df8aede1fc70317171da2f08027a96a51ae37c6 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 15:44:01 +0100 Subject: [PATCH 358/424] interrogatedb: add interrogate_element_is_sequence/mapping --- dtool/src/interrogatedb/interrogate_interface.cxx | 12 ++++++++++++ dtool/src/interrogatedb/interrogate_interface.h | 3 +++ 2 files changed, 15 insertions(+) diff --git a/dtool/src/interrogatedb/interrogate_interface.cxx b/dtool/src/interrogatedb/interrogate_interface.cxx index 2cd08d4adf..3fc2053855 100644 --- a/dtool/src/interrogatedb/interrogate_interface.cxx +++ b/dtool/src/interrogatedb/interrogate_interface.cxx @@ -176,6 +176,18 @@ interrogate_element_setter(ElementIndex element) { return InterrogateDatabase::get_ptr()->get_element(element).get_setter(); } +bool +interrogate_element_is_sequence(ElementIndex element) { + // cerr << "interrogate_element_is_sequence(" << element << ")\n"; + return InterrogateDatabase::get_ptr()->get_element(element).is_sequence(); +} + +bool +interrogate_element_is_mapping(ElementIndex element) { + // cerr << "interrogate_element_is_mapping(" << element << ")\n"; + return InterrogateDatabase::get_ptr()->get_element(element).is_mapping(); +} + int interrogate_number_of_globals() { // cerr << "interrogate_number_of_globals()\n"; diff --git a/dtool/src/interrogatedb/interrogate_interface.h b/dtool/src/interrogatedb/interrogate_interface.h index 6c434d7833..cc88af041d 100644 --- a/dtool/src/interrogatedb/interrogate_interface.h +++ b/dtool/src/interrogatedb/interrogate_interface.h @@ -153,6 +153,9 @@ EXPCL_INTERROGATEDB FunctionIndex interrogate_element_getter(ElementIndex elemen EXPCL_INTERROGATEDB bool interrogate_element_has_setter(ElementIndex element); EXPCL_INTERROGATEDB FunctionIndex interrogate_element_setter(ElementIndex element); +EXPCL_INTERROGATEDB bool interrogate_element_is_sequence(ElementIndex element); +EXPCL_INTERROGATEDB bool interrogate_element_is_mapping(ElementIndex element); + // Global Data // This is the list of global data elements. From 256c8c5a23a20331d8c88a4e5074c13716792153 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 15:44:59 +0100 Subject: [PATCH 359/424] interrogate: don't add space at end of command-line comment --- dtool/src/interrogate/interrogate.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dtool/src/interrogate/interrogate.cxx b/dtool/src/interrogate/interrogate.cxx index ef06833324..e3cab85a6b 100644 --- a/dtool/src/interrogate/interrogate.cxx +++ b/dtool/src/interrogate/interrogate.cxx @@ -312,7 +312,10 @@ main(int argc, char **argv) { string command_line; int i; for (i = 0; i < argc; i++) { - command_line += string(argv[i]) + " "; + if (i > 0) { + command_line += ' '; + } + command_line += string(argv[i]); } Filename fn; From 47188b47f6b71530ed351c57653cfe47d8cf0e3d Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 15:45:30 +0100 Subject: [PATCH 360/424] interrogate: fix recording type of property in interrogatedb --- dtool/src/interrogate/interrogateBuilder.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/dtool/src/interrogate/interrogateBuilder.cxx b/dtool/src/interrogate/interrogateBuilder.cxx index b740d59e1a..da0e2a5318 100644 --- a/dtool/src/interrogate/interrogateBuilder.cxx +++ b/dtool/src/interrogate/interrogateBuilder.cxx @@ -2024,6 +2024,7 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP if (iproperty._type != 0 && iproperty._type != return_index) { cerr << "Property " << property_name << " has inconsistent element type!\n"; } + iproperty._type = return_index; } else { iproperty._type = 0; } From a24c8171447003996361fd74c2119890cc84b021 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 15:46:09 +0100 Subject: [PATCH 361/424] interrogatedb: regenerate Python bindings --- dtool/metalibs/dtoolconfig/pydtool.cxx | 584 +++++++++++++------------ 1 file changed, 308 insertions(+), 276 deletions(-) diff --git a/dtool/metalibs/dtoolconfig/pydtool.cxx b/dtool/metalibs/dtoolconfig/pydtool.cxx index 7f2e0185f6..39719b385d 100644 --- a/dtool/metalibs/dtoolconfig/pydtool.cxx +++ b/dtool/metalibs/dtoolconfig/pydtool.cxx @@ -17,7 +17,7 @@ #if PYTHON_FRAMEWORK #include #else - #include + #include "Python.h" #endif static PyObject *_inP07yttbRf(PyObject *self, PyObject *args); @@ -45,6 +45,8 @@ static PyObject *_inP07ytDId0(PyObject *self, PyObject *args); static PyObject *_inP07ytHuAm(PyObject *self, PyObject *args); static PyObject *_inP07yt_xr0(PyObject *self, PyObject *args); static PyObject *_inP07ytH5qp(PyObject *self, PyObject *args); +static PyObject *_inP07ytq45U(PyObject *self, PyObject *args); +static PyObject *_inP07yt6IPa(PyObject *self, PyObject *args); static PyObject *_inP07ytU2_B(PyObject *self, PyObject *args); static PyObject *_inP07ytHFO2(PyObject *self, PyObject *args); static PyObject *_inP07ytcfjm(PyObject *self, PyObject *args); @@ -166,10 +168,10 @@ static PyObject * _inP07yttbRf(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - interrogate_add_search_directory((char const *)param0); + ::interrogate_add_search_directory((char const *)param0); return Py_BuildValue(""); } - return (PyObject *)NULL; + return nullptr; } /* @@ -180,10 +182,10 @@ static PyObject * _inP07ytda_g(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - interrogate_add_search_path((char const *)param0); + ::interrogate_add_search_path((char const *)param0); return Py_BuildValue(""); } - return (PyObject *)NULL; + return nullptr; } /* @@ -193,10 +195,10 @@ _inP07ytda_g(PyObject *, PyObject *args) { static PyObject * _inP07yt4RgX(PyObject *, PyObject *args) { if (PyArg_ParseTuple(args, "")) { - bool return_value = interrogate_error_flag(); + bool return_value = ::interrogate_error_flag(); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -206,14 +208,14 @@ _inP07yt4RgX(PyObject *, PyObject *args) { static PyObject * _inP07yt3Gip(PyObject *, PyObject *args) { if (PyArg_ParseTuple(args, "")) { - int return_value = interrogate_number_of_manifests(); + int return_value = ::interrogate_number_of_manifests(); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -224,14 +226,14 @@ static PyObject * _inP07ytRKDz(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - ManifestIndex return_value = interrogate_get_manifest((int)param0); + ManifestIndex return_value = ::interrogate_get_manifest((int)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -242,14 +244,14 @@ static PyObject * _inP07ytgZ9N(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - ManifestIndex return_value = interrogate_get_manifest_by_name((char const *)param0); + ManifestIndex return_value = ::interrogate_get_manifest_by_name((char const *)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -260,14 +262,14 @@ static PyObject * _inP07ytFnRZ(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_manifest_name((ManifestIndex)param0); + char const *return_value = ::interrogate_manifest_name((ManifestIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -278,14 +280,14 @@ static PyObject * _inP07ytg0Qv(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_manifest_definition((ManifestIndex)param0); + char const *return_value = ::interrogate_manifest_definition((ManifestIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -296,10 +298,10 @@ static PyObject * _inP07yttrqw(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_manifest_has_type((ManifestIndex)param0); + bool return_value = ::interrogate_manifest_has_type((ManifestIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -310,14 +312,14 @@ static PyObject * _inP07ytdmpW(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - TypeIndex return_value = interrogate_manifest_get_type((ManifestIndex)param0); + TypeIndex return_value = ::interrogate_manifest_get_type((ManifestIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -328,10 +330,10 @@ static PyObject * _inP07ytUYgQ(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_manifest_has_getter((ManifestIndex)param0); + bool return_value = ::interrogate_manifest_has_getter((ManifestIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -342,14 +344,14 @@ static PyObject * _inP07yt0k7F(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - FunctionIndex return_value = interrogate_manifest_getter((ManifestIndex)param0); + FunctionIndex return_value = ::interrogate_manifest_getter((ManifestIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -360,10 +362,10 @@ static PyObject * _inP07ytfIsr(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_manifest_has_int_value((ManifestIndex)param0); + bool return_value = ::interrogate_manifest_has_int_value((ManifestIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -374,14 +376,14 @@ static PyObject * _inP07ytvysR(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_manifest_get_int_value((ManifestIndex)param0); + int return_value = ::interrogate_manifest_get_int_value((ManifestIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -392,14 +394,14 @@ static PyObject * _inP07ytYQ_2(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_element_name((ElementIndex)param0); + char const *return_value = ::interrogate_element_name((ElementIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -410,14 +412,14 @@ static PyObject * _inP07yt3kdv(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_element_scoped_name((ElementIndex)param0); + char const *return_value = ::interrogate_element_scoped_name((ElementIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -428,10 +430,10 @@ static PyObject * _inP07ytew01(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_element_has_comment((ElementIndex)param0); + bool return_value = ::interrogate_element_has_comment((ElementIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -442,14 +444,14 @@ static PyObject * _inP07ytQna7(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_element_comment((ElementIndex)param0); + char const *return_value = ::interrogate_element_comment((ElementIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -460,14 +462,14 @@ static PyObject * _inP07ytkg95(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - ElementIndex return_value = interrogate_get_element_by_name((char const *)param0); + ElementIndex return_value = ::interrogate_get_element_by_name((char const *)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -478,14 +480,14 @@ static PyObject * _inP07ytluRc(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - ElementIndex return_value = interrogate_get_element_by_scoped_name((char const *)param0); + ElementIndex return_value = ::interrogate_get_element_by_scoped_name((char const *)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -496,14 +498,14 @@ static PyObject * _inP07yttHdM(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - TypeIndex return_value = interrogate_element_type((ElementIndex)param0); + TypeIndex return_value = ::interrogate_element_type((ElementIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -514,10 +516,10 @@ static PyObject * _inP07ytDId0(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_element_has_getter((ElementIndex)param0); + bool return_value = ::interrogate_element_has_getter((ElementIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -528,14 +530,14 @@ static PyObject * _inP07ytHuAm(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - FunctionIndex return_value = interrogate_element_getter((ElementIndex)param0); + FunctionIndex return_value = ::interrogate_element_getter((ElementIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -546,10 +548,10 @@ static PyObject * _inP07yt_xr0(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_element_has_setter((ElementIndex)param0); + bool return_value = ::interrogate_element_has_setter((ElementIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -560,14 +562,42 @@ static PyObject * _inP07ytH5qp(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - FunctionIndex return_value = interrogate_element_setter((ElementIndex)param0); + FunctionIndex return_value = ::interrogate_element_setter((ElementIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; +} + +/* + * Python simple wrapper for + * bool interrogate_element_is_sequence(ElementIndex element) + */ +static PyObject * +_inP07ytq45U(PyObject *, PyObject *args) { + int param0; + if (PyArg_ParseTuple(args, "i", ¶m0)) { + bool return_value = ::interrogate_element_is_sequence((ElementIndex)param0); + return PyBool_FromLong(return_value); + } + return nullptr; +} + +/* + * Python simple wrapper for + * bool interrogate_element_is_mapping(ElementIndex element) + */ +static PyObject * +_inP07yt6IPa(PyObject *, PyObject *args) { + int param0; + if (PyArg_ParseTuple(args, "i", ¶m0)) { + bool return_value = ::interrogate_element_is_mapping((ElementIndex)param0); + return PyBool_FromLong(return_value); + } + return nullptr; } /* @@ -577,14 +607,14 @@ _inP07ytH5qp(PyObject *, PyObject *args) { static PyObject * _inP07ytU2_B(PyObject *, PyObject *args) { if (PyArg_ParseTuple(args, "")) { - int return_value = interrogate_number_of_globals(); + int return_value = ::interrogate_number_of_globals(); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -595,14 +625,14 @@ static PyObject * _inP07ytHFO2(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - ElementIndex return_value = interrogate_get_global((int)param0); + ElementIndex return_value = ::interrogate_get_global((int)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -612,14 +642,14 @@ _inP07ytHFO2(PyObject *, PyObject *args) { static PyObject * _inP07ytcfjm(PyObject *, PyObject *args) { if (PyArg_ParseTuple(args, "")) { - int return_value = interrogate_number_of_global_functions(); + int return_value = ::interrogate_number_of_global_functions(); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -630,14 +660,14 @@ static PyObject * _inP07yt3Sjw(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - FunctionIndex return_value = interrogate_get_global_function((int)param0); + FunctionIndex return_value = ::interrogate_get_global_function((int)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -647,14 +677,14 @@ _inP07yt3Sjw(PyObject *, PyObject *args) { static PyObject * _inP07ytgJcX(PyObject *, PyObject *args) { if (PyArg_ParseTuple(args, "")) { - int return_value = interrogate_number_of_functions(); + int return_value = ::interrogate_number_of_functions(); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -665,14 +695,14 @@ static PyObject * _inP07ytYlw6(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - FunctionIndex return_value = interrogate_get_function((int)param0); + FunctionIndex return_value = ::interrogate_get_function((int)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -683,14 +713,14 @@ static PyObject * _inP07ytsmnz(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_function_name((FunctionIndex)param0); + char const *return_value = ::interrogate_function_name((FunctionIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -701,14 +731,14 @@ static PyObject * _inP07ytxQ10(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_function_scoped_name((FunctionIndex)param0); + char const *return_value = ::interrogate_function_scoped_name((FunctionIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -719,10 +749,10 @@ static PyObject * _inP07yt6gPB(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_function_has_comment((FunctionIndex)param0); + bool return_value = ::interrogate_function_has_comment((FunctionIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -733,14 +763,14 @@ static PyObject * _inP07ytISgV(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_function_comment((FunctionIndex)param0); + char const *return_value = ::interrogate_function_comment((FunctionIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -751,14 +781,14 @@ static PyObject * _inP07ytH3bx(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_function_prototype((FunctionIndex)param0); + char const *return_value = ::interrogate_function_prototype((FunctionIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -769,10 +799,10 @@ static PyObject * _inP07ytzeUk(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_function_is_method((FunctionIndex)param0); + bool return_value = ::interrogate_function_is_method((FunctionIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -783,14 +813,14 @@ static PyObject * _inP07ytUeI5(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - TypeIndex return_value = interrogate_function_class((FunctionIndex)param0); + TypeIndex return_value = ::interrogate_function_class((FunctionIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -801,10 +831,10 @@ static PyObject * _inP07ytuSvx(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_function_has_module_name((FunctionIndex)param0); + bool return_value = ::interrogate_function_has_module_name((FunctionIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -815,14 +845,14 @@ static PyObject * _inP07ytwpYd(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_function_module_name((FunctionIndex)param0); + char const *return_value = ::interrogate_function_module_name((FunctionIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -833,10 +863,10 @@ static PyObject * _inP07ytOfNh(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_function_has_library_name((FunctionIndex)param0); + bool return_value = ::interrogate_function_has_library_name((FunctionIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -847,14 +877,14 @@ static PyObject * _inP07ytf5_U(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_function_library_name((FunctionIndex)param0); + char const *return_value = ::interrogate_function_library_name((FunctionIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -865,10 +895,10 @@ static PyObject * _inP07ytL3ZB(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_function_is_virtual((FunctionIndex)param0); + bool return_value = ::interrogate_function_is_virtual((FunctionIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -879,14 +909,14 @@ static PyObject * _inP07ytXw0I(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_function_number_of_c_wrappers((FunctionIndex)param0); + int return_value = ::interrogate_function_number_of_c_wrappers((FunctionIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -898,14 +928,14 @@ _inP07yt3zru(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - FunctionWrapperIndex return_value = interrogate_function_c_wrapper((FunctionIndex)param0, (int)param1); + FunctionWrapperIndex return_value = ::interrogate_function_c_wrapper((FunctionIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -916,14 +946,14 @@ static PyObject * _inP07ytRrg2(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_function_number_of_python_wrappers((FunctionIndex)param0); + int return_value = ::interrogate_function_number_of_python_wrappers((FunctionIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -935,14 +965,14 @@ _inP07ytEJCx(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - FunctionWrapperIndex return_value = interrogate_function_python_wrapper((FunctionIndex)param0, (int)param1); + FunctionWrapperIndex return_value = ::interrogate_function_python_wrapper((FunctionIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -953,14 +983,14 @@ static PyObject * _inP07ytWAZr(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_wrapper_name((FunctionWrapperIndex)param0); + char const *return_value = ::interrogate_wrapper_name((FunctionWrapperIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -971,10 +1001,10 @@ static PyObject * _inP07ytrD_M(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_wrapper_is_callable_by_name((FunctionWrapperIndex)param0); + bool return_value = ::interrogate_wrapper_is_callable_by_name((FunctionWrapperIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -985,10 +1015,10 @@ static PyObject * _inP07ytjolz(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_wrapper_has_comment((FunctionWrapperIndex)param0); + bool return_value = ::interrogate_wrapper_has_comment((FunctionWrapperIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -999,14 +1029,14 @@ static PyObject * _inP07ytt_JD(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_wrapper_comment((FunctionWrapperIndex)param0); + char const *return_value = ::interrogate_wrapper_comment((FunctionWrapperIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1017,10 +1047,10 @@ static PyObject * _inP07ytwEts(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_wrapper_has_return_value((FunctionWrapperIndex)param0); + bool return_value = ::interrogate_wrapper_has_return_value((FunctionWrapperIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1031,14 +1061,14 @@ static PyObject * _inP07ytrJWs(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - TypeIndex return_value = interrogate_wrapper_return_type((FunctionWrapperIndex)param0); + TypeIndex return_value = ::interrogate_wrapper_return_type((FunctionWrapperIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1049,10 +1079,10 @@ static PyObject * _inP07ytpmFD(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_wrapper_caller_manages_return_value((FunctionWrapperIndex)param0); + bool return_value = ::interrogate_wrapper_caller_manages_return_value((FunctionWrapperIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1063,14 +1093,14 @@ static PyObject * _inP07ytyYUX(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - FunctionIndex return_value = interrogate_wrapper_return_value_destructor((FunctionWrapperIndex)param0); + FunctionIndex return_value = ::interrogate_wrapper_return_value_destructor((FunctionWrapperIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1081,14 +1111,14 @@ static PyObject * _inP07yt54dn(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_wrapper_number_of_parameters((FunctionWrapperIndex)param0); + int return_value = ::interrogate_wrapper_number_of_parameters((FunctionWrapperIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1100,14 +1130,14 @@ _inP07ytGMpW(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - TypeIndex return_value = interrogate_wrapper_parameter_type((FunctionWrapperIndex)param0, (int)param1); + TypeIndex return_value = ::interrogate_wrapper_parameter_type((FunctionWrapperIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1119,10 +1149,10 @@ _inP07ytNuBV(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - bool return_value = interrogate_wrapper_parameter_has_name((FunctionWrapperIndex)param0, (int)param1); + bool return_value = ::interrogate_wrapper_parameter_has_name((FunctionWrapperIndex)param0, (int)param1); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1134,14 +1164,14 @@ _inP07yt9UwA(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - char const *return_value = interrogate_wrapper_parameter_name((FunctionWrapperIndex)param0, (int)param1); + char const *return_value = ::interrogate_wrapper_parameter_name((FunctionWrapperIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1153,10 +1183,10 @@ _inP07yt3FDt(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - bool return_value = interrogate_wrapper_parameter_is_this((FunctionWrapperIndex)param0, (int)param1); + bool return_value = ::interrogate_wrapper_parameter_is_this((FunctionWrapperIndex)param0, (int)param1); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1167,10 +1197,10 @@ static PyObject * _inP07ytf513(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_wrapper_has_pointer((FunctionWrapperIndex)param0); + bool return_value = ::interrogate_wrapper_has_pointer((FunctionWrapperIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1181,10 +1211,10 @@ static PyObject * _inP07ytsqGH(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - interrogate_wrapper_pointer((FunctionWrapperIndex)param0); + ::interrogate_wrapper_pointer((FunctionWrapperIndex)param0); return Py_BuildValue(""); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1195,14 +1225,14 @@ static PyObject * _inP07yt7shV(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_wrapper_unique_name((FunctionWrapperIndex)param0); + char const *return_value = ::interrogate_wrapper_unique_name((FunctionWrapperIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1213,14 +1243,14 @@ static PyObject * _inP07ytA1eF(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - FunctionWrapperIndex return_value = interrogate_get_wrapper_by_unique_name((char const *)param0); + FunctionWrapperIndex return_value = ::interrogate_get_wrapper_by_unique_name((char const *)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1231,14 +1261,14 @@ static PyObject * _inP07yt776V(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_make_seq_seq_name((MakeSeqIndex)param0); + char const *return_value = ::interrogate_make_seq_seq_name((MakeSeqIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1249,14 +1279,14 @@ static PyObject * _inP07ytryup(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_make_seq_scoped_name((MakeSeqIndex)param0); + char const *return_value = ::interrogate_make_seq_scoped_name((MakeSeqIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1267,10 +1297,10 @@ static PyObject * _inP07ytiytI(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_make_seq_has_comment((ElementIndex)param0); + bool return_value = ::interrogate_make_seq_has_comment((ElementIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1281,14 +1311,14 @@ static PyObject * _inP07ytZc07(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_make_seq_comment((ElementIndex)param0); + char const *return_value = ::interrogate_make_seq_comment((ElementIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1299,14 +1329,14 @@ static PyObject * _inP07ytfaH0(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_make_seq_num_name((MakeSeqIndex)param0); + char const *return_value = ::interrogate_make_seq_num_name((MakeSeqIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1317,14 +1347,14 @@ static PyObject * _inP07ytGB9D(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_make_seq_element_name((MakeSeqIndex)param0); + char const *return_value = ::interrogate_make_seq_element_name((MakeSeqIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1334,14 +1364,14 @@ _inP07ytGB9D(PyObject *, PyObject *args) { static PyObject * _inP07ytsxxs(PyObject *, PyObject *args) { if (PyArg_ParseTuple(args, "")) { - int return_value = interrogate_number_of_global_types(); + int return_value = ::interrogate_number_of_global_types(); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1352,14 +1382,14 @@ static PyObject * _inP07ytMT0z(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - TypeIndex return_value = interrogate_get_global_type((int)param0); + TypeIndex return_value = ::interrogate_get_global_type((int)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1369,14 +1399,14 @@ _inP07ytMT0z(PyObject *, PyObject *args) { static PyObject * _inP07ytiW3v(PyObject *, PyObject *args) { if (PyArg_ParseTuple(args, "")) { - int return_value = interrogate_number_of_types(); + int return_value = ::interrogate_number_of_types(); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1387,14 +1417,14 @@ static PyObject * _inP07yt4Px8(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - TypeIndex return_value = interrogate_get_type((int)param0); + TypeIndex return_value = ::interrogate_get_type((int)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1405,14 +1435,14 @@ static PyObject * _inP07ytNHcs(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - TypeIndex return_value = interrogate_get_type_by_name((char const *)param0); + TypeIndex return_value = ::interrogate_get_type_by_name((char const *)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1423,14 +1453,14 @@ static PyObject * _inP07ytqHrb(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - TypeIndex return_value = interrogate_get_type_by_scoped_name((char const *)param0); + TypeIndex return_value = ::interrogate_get_type_by_scoped_name((char const *)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1441,14 +1471,14 @@ static PyObject * _inP07ytaOqq(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - TypeIndex return_value = interrogate_get_type_by_true_name((char const *)param0); + TypeIndex return_value = ::interrogate_get_type_by_true_name((char const *)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1459,10 +1489,10 @@ static PyObject * _inP07ytpTBb(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_global((TypeIndex)param0); + bool return_value = ::interrogate_type_is_global((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1473,14 +1503,14 @@ static PyObject * _inP07ytqWOw(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_type_name((TypeIndex)param0); + char const *return_value = ::interrogate_type_name((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1491,14 +1521,14 @@ static PyObject * _inP07ytHu7x(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_type_scoped_name((TypeIndex)param0); + char const *return_value = ::interrogate_type_scoped_name((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1509,14 +1539,14 @@ static PyObject * _inP07ytwGnA(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_type_true_name((TypeIndex)param0); + char const *return_value = ::interrogate_type_true_name((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1527,10 +1557,10 @@ static PyObject * _inP07ytXGxx(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_nested((TypeIndex)param0); + bool return_value = ::interrogate_type_is_nested((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1541,14 +1571,14 @@ static PyObject * _inP07ytj04Z(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - TypeIndex return_value = interrogate_type_outer_class((TypeIndex)param0); + TypeIndex return_value = ::interrogate_type_outer_class((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1559,10 +1589,10 @@ static PyObject * _inP07ytEOv4(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_has_comment((TypeIndex)param0); + bool return_value = ::interrogate_type_has_comment((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1573,14 +1603,14 @@ static PyObject * _inP07ytpCqJ(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_type_comment((TypeIndex)param0); + char const *return_value = ::interrogate_type_comment((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1591,10 +1621,10 @@ static PyObject * _inP07yt_Pz3(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_has_module_name((TypeIndex)param0); + bool return_value = ::interrogate_type_has_module_name((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1605,14 +1635,14 @@ static PyObject * _inP07ytt_06(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_type_module_name((TypeIndex)param0); + char const *return_value = ::interrogate_type_module_name((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1623,10 +1653,10 @@ static PyObject * _inP07ytmuPs(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_has_library_name((TypeIndex)param0); + bool return_value = ::interrogate_type_has_library_name((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1637,14 +1667,14 @@ static PyObject * _inP07ytvM8B(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - char const *return_value = interrogate_type_library_name((TypeIndex)param0); + char const *return_value = ::interrogate_type_library_name((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1655,10 +1685,10 @@ static PyObject * _inP07ytap97(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_atomic((TypeIndex)param0); + bool return_value = ::interrogate_type_is_atomic((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1669,14 +1699,14 @@ static PyObject * _inP07yt0o8D(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - AtomicToken return_value = interrogate_type_atomic_token((TypeIndex)param0); + AtomicToken return_value = ::interrogate_type_atomic_token((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1687,10 +1717,10 @@ static PyObject * _inP07ytOoQ2(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_unsigned((TypeIndex)param0); + bool return_value = ::interrogate_type_is_unsigned((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1701,10 +1731,10 @@ static PyObject * _inP07ytKuFh(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_signed((TypeIndex)param0); + bool return_value = ::interrogate_type_is_signed((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1715,10 +1745,10 @@ static PyObject * _inP07yto5L6(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_long((TypeIndex)param0); + bool return_value = ::interrogate_type_is_long((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1729,10 +1759,10 @@ static PyObject * _inP07ytzgKK(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_longlong((TypeIndex)param0); + bool return_value = ::interrogate_type_is_longlong((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1743,10 +1773,10 @@ static PyObject * _inP07yt0FIF(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_short((TypeIndex)param0); + bool return_value = ::interrogate_type_is_short((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1757,10 +1787,10 @@ static PyObject * _inP07ytZqvD(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_wrapped((TypeIndex)param0); + bool return_value = ::interrogate_type_is_wrapped((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1771,10 +1801,10 @@ static PyObject * _inP07ytDyRd(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_pointer((TypeIndex)param0); + bool return_value = ::interrogate_type_is_pointer((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1785,10 +1815,10 @@ static PyObject * _inP07ytMnKa(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_const((TypeIndex)param0); + bool return_value = ::interrogate_type_is_const((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1799,10 +1829,10 @@ static PyObject * _inP07ytRtji(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_typedef((TypeIndex)param0); + bool return_value = ::interrogate_type_is_typedef((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1813,14 +1843,14 @@ static PyObject * _inP07ytCnbQ(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - TypeIndex return_value = interrogate_type_wrapped_type((TypeIndex)param0); + TypeIndex return_value = ::interrogate_type_wrapped_type((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1831,10 +1861,10 @@ static PyObject * _inP07ytdUVN(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_enum((TypeIndex)param0); + bool return_value = ::interrogate_type_is_enum((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1845,14 +1875,14 @@ static PyObject * _inP07ytihbt(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_type_number_of_enum_values((TypeIndex)param0); + int return_value = ::interrogate_type_number_of_enum_values((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1864,14 +1894,14 @@ _inP07ytbyPY(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - char const *return_value = interrogate_type_enum_value_name((TypeIndex)param0, (int)param1); + char const *return_value = ::interrogate_type_enum_value_name((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1883,14 +1913,14 @@ _inP07ytAaT6(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - char const *return_value = interrogate_type_enum_value_scoped_name((TypeIndex)param0, (int)param1); + char const *return_value = ::interrogate_type_enum_value_scoped_name((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1902,14 +1932,14 @@ _inP07ytgL9q(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - char const *return_value = interrogate_type_enum_value_comment((TypeIndex)param0, (int)param1); + char const *return_value = ::interrogate_type_enum_value_comment((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(return_value); #else return PyString_FromString(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1921,14 +1951,14 @@ _inP07ytWB97(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - int return_value = interrogate_type_enum_value((TypeIndex)param0, (int)param1); + int return_value = ::interrogate_type_enum_value((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -1939,10 +1969,10 @@ static PyObject * _inP07ytDUAl(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_struct((TypeIndex)param0); + bool return_value = ::interrogate_type_is_struct((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1953,10 +1983,10 @@ static PyObject * _inP07yt1_Kf(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_class((TypeIndex)param0); + bool return_value = ::interrogate_type_is_class((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1967,10 +1997,10 @@ static PyObject * _inP07yt98lD(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_union((TypeIndex)param0); + bool return_value = ::interrogate_type_is_union((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1981,10 +2011,10 @@ static PyObject * _inP07yt9SHr(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_fully_defined((TypeIndex)param0); + bool return_value = ::interrogate_type_is_fully_defined((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -1995,10 +2025,10 @@ static PyObject * _inP07ytdiZP(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_is_unpublished((TypeIndex)param0); + bool return_value = ::interrogate_type_is_unpublished((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -2009,14 +2039,14 @@ static PyObject * _inP07ytTdER(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_type_number_of_constructors((TypeIndex)param0); + int return_value = ::interrogate_type_number_of_constructors((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2028,14 +2058,14 @@ _inP07ytYO56(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - FunctionIndex return_value = interrogate_type_get_constructor((TypeIndex)param0, (int)param1); + FunctionIndex return_value = ::interrogate_type_get_constructor((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2046,10 +2076,10 @@ static PyObject * _inP07ytxtCG(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_has_destructor((TypeIndex)param0); + bool return_value = ::interrogate_type_has_destructor((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -2060,10 +2090,10 @@ static PyObject * _inP07yt_EB2(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - bool return_value = interrogate_type_destructor_is_inherited((TypeIndex)param0); + bool return_value = ::interrogate_type_destructor_is_inherited((TypeIndex)param0); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -2074,14 +2104,14 @@ static PyObject * _inP07ytEG1l(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - FunctionIndex return_value = interrogate_type_get_destructor((TypeIndex)param0); + FunctionIndex return_value = ::interrogate_type_get_destructor((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2092,14 +2122,14 @@ static PyObject * _inP07yt7tUq(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_type_number_of_elements((TypeIndex)param0); + int return_value = ::interrogate_type_number_of_elements((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2111,14 +2141,14 @@ _inP07ytyStU(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - ElementIndex return_value = interrogate_type_get_element((TypeIndex)param0, (int)param1); + ElementIndex return_value = ::interrogate_type_get_element((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2129,14 +2159,14 @@ static PyObject * _inP07ytdM85(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_type_number_of_methods((TypeIndex)param0); + int return_value = ::interrogate_type_number_of_methods((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2148,14 +2178,14 @@ _inP07ytk_GN(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - FunctionIndex return_value = interrogate_type_get_method((TypeIndex)param0, (int)param1); + FunctionIndex return_value = ::interrogate_type_get_method((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2166,14 +2196,14 @@ static PyObject * _inP07yt8QjG(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_type_number_of_make_seqs((TypeIndex)param0); + int return_value = ::interrogate_type_number_of_make_seqs((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2185,14 +2215,14 @@ _inP07ytyMtj(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - MakeSeqIndex return_value = interrogate_type_get_make_seq((TypeIndex)param0, (int)param1); + MakeSeqIndex return_value = ::interrogate_type_get_make_seq((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2203,14 +2233,14 @@ static PyObject * _inP07ytHDtN(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_type_number_of_casts((TypeIndex)param0); + int return_value = ::interrogate_type_number_of_casts((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2222,14 +2252,14 @@ _inP07ytHFjA(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - FunctionIndex return_value = interrogate_type_get_cast((TypeIndex)param0, (int)param1); + FunctionIndex return_value = ::interrogate_type_get_cast((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2240,14 +2270,14 @@ static PyObject * _inP07yt_NPR(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_type_number_of_derivations((TypeIndex)param0); + int return_value = ::interrogate_type_number_of_derivations((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2259,14 +2289,14 @@ _inP07ytcTOH(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - TypeIndex return_value = interrogate_type_get_derivation((TypeIndex)param0, (int)param1); + TypeIndex return_value = ::interrogate_type_get_derivation((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2278,10 +2308,10 @@ _inP07ythdU7(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - bool return_value = interrogate_type_derivation_has_upcast((TypeIndex)param0, (int)param1); + bool return_value = ::interrogate_type_derivation_has_upcast((TypeIndex)param0, (int)param1); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -2293,14 +2323,14 @@ _inP07ytQPxU(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - FunctionIndex return_value = interrogate_type_get_upcast((TypeIndex)param0, (int)param1); + FunctionIndex return_value = ::interrogate_type_get_upcast((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2312,10 +2342,10 @@ _inP07ytO7Pz(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - bool return_value = interrogate_type_derivation_downcast_is_impossible((TypeIndex)param0, (int)param1); + bool return_value = ::interrogate_type_derivation_downcast_is_impossible((TypeIndex)param0, (int)param1); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -2327,10 +2357,10 @@ _inP07ytvu_E(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - bool return_value = interrogate_type_derivation_has_downcast((TypeIndex)param0, (int)param1); + bool return_value = ::interrogate_type_derivation_has_downcast((TypeIndex)param0, (int)param1); return PyBool_FromLong(return_value); } - return (PyObject *)NULL; + return nullptr; } /* @@ -2342,14 +2372,14 @@ _inP07ytxGUt(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - FunctionIndex return_value = interrogate_type_get_downcast((TypeIndex)param0, (int)param1); + FunctionIndex return_value = ::interrogate_type_get_downcast((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2360,14 +2390,14 @@ static PyObject * _inP07ytzM1P(PyObject *, PyObject *args) { int param0; if (PyArg_ParseTuple(args, "i", ¶m0)) { - int return_value = interrogate_type_number_of_nested_types((TypeIndex)param0); + int return_value = ::interrogate_type_number_of_nested_types((TypeIndex)param0); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2379,14 +2409,14 @@ _inP07ytoY5L(PyObject *, PyObject *args) { int param0; int param1; if (PyArg_ParseTuple(args, "ii", ¶m0, ¶m1)) { - TypeIndex return_value = interrogate_type_get_nested_type((TypeIndex)param0, (int)param1); + TypeIndex return_value = ::interrogate_type_get_nested_type((TypeIndex)param0, (int)param1); #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(return_value); #else return PyInt_FromLong(return_value); #endif } - return (PyObject *)NULL; + return nullptr; } /* @@ -2397,10 +2427,10 @@ static PyObject * _inP07yte_7S(PyObject *, PyObject *args) { char *param0; if (PyArg_ParseTuple(args, "s", ¶m0)) { - interrogate_request_database((char const *)param0); + ::interrogate_request_database((char const *)param0); return Py_BuildValue(""); } - return (PyObject *)NULL; + return nullptr; } /* @@ -2411,10 +2441,10 @@ static PyObject * _inP07ytw_15(PyObject *, PyObject *args) { Py_ssize_t param0; if (PyArg_ParseTuple(args, "n", ¶m0)) { - interrogate_request_module((InterrogateModuleDef *)param0); + ::interrogate_request_module((InterrogateModuleDef *)param0); return Py_BuildValue(""); } - return (PyObject *)NULL; + return nullptr; } @@ -2444,6 +2474,8 @@ static PyMethodDef python_simple_funcs[] = { { "interrogate_element_getter", &_inP07ytHuAm, METH_VARARGS }, { "interrogate_element_has_setter", &_inP07yt_xr0, METH_VARARGS }, { "interrogate_element_setter", &_inP07ytH5qp, METH_VARARGS }, + { "interrogate_element_is_sequence", &_inP07ytq45U, METH_VARARGS }, + { "interrogate_element_is_mapping", &_inP07yt6IPa, METH_VARARGS }, { "interrogate_number_of_globals", &_inP07ytU2_B, METH_VARARGS }, { "interrogate_get_global", &_inP07ytHFO2, METH_VARARGS }, { "interrogate_number_of_global_functions", &_inP07ytcfjm, METH_VARARGS }, @@ -2555,17 +2587,17 @@ static PyMethodDef python_simple_funcs[] = { { "interrogate_type_get_nested_type", &_inP07ytoY5L, METH_VARARGS }, { "interrogate_request_database", &_inP07yte_7S, METH_VARARGS }, { "interrogate_request_module", &_inP07ytw_15, METH_VARARGS }, - { NULL, NULL } + { nullptr, nullptr } }; #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef python_simple_module = { PyModuleDef_HEAD_INIT, "interrogatedb", - NULL, + nullptr, -1, python_simple_funcs, - NULL, NULL, NULL, NULL + nullptr, nullptr, nullptr, nullptr }; #define INIT_FUNC PyObject *PyInit_interrogatedb From c0c202a423d15cec50535311cc22b635a7677f6a Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 15:47:44 +0100 Subject: [PATCH 362/424] directscripts: bring Python documentation generation up-to-date Fixes #484 --- direct/src/directscripts/Doxyfile.python | 4 +- direct/src/directscripts/extract_docs.py | 130 ++++++++++++++--------- 2 files changed, 82 insertions(+), 52 deletions(-) diff --git a/direct/src/directscripts/Doxyfile.python b/direct/src/directscripts/Doxyfile.python index 4bcfb58727..5b2e73ff77 100644 --- a/direct/src/directscripts/Doxyfile.python +++ b/direct/src/directscripts/Doxyfile.python @@ -665,7 +665,9 @@ EXCLUDE_SYMLINKS = NO # for example use the pattern */test/* EXCLUDE_PATTERNS = */Opt*-*/* \ - */CVS/* + */CVS/* \ + */.git/* \ + */__pycache__/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the diff --git a/direct/src/directscripts/extract_docs.py b/direct/src/directscripts/extract_docs.py index b6ad69e946..caead7e4dc 100644 --- a/direct/src/directscripts/extract_docs.py +++ b/direct/src/directscripts/extract_docs.py @@ -9,9 +9,20 @@ from __future__ import print_function __all__ = [] -import os +import os, sys +from distutils import sysconfig import panda3d, pandac -from panda3d.dtoolconfig import * +from panda3d.interrogatedb import * + + +if 'interrogate_element_is_sequence' not in globals(): + def interrogate_element_is_sequence(element): + return False + +if 'interrogate_element_is_mapping' not in globals(): + def interrogate_element_is_mapping(element): + return False + LICENSE = """PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University. All rights reserved. @@ -19,6 +30,16 @@ 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.\"""".split("\n") +MAINPAGE = """@mainpage Panda3D Python API Reference +Welcome to the Panda3D API reference. + +Use the links at the top of this page to browse through the list of modules or +the list of classes. + +This reference is automatically generated from comments in the source code. +""" + + def comment(code): if not code: return "" @@ -45,49 +66,16 @@ def comment(code): return '' def block_comment(code): - if not code: + code = code.strip() + + if not code.startswith('///<') and '@verbatim' not in code: + code = code.replace('<', '\\<').replace('>', '\\>') + + if not code or code[0] != '/': + # Not really a comment; get rid of it. return "" - lines = code.split("\n") - newlines = [] - indent = 0 - reading_desc = False - - for line in lines: - if line.startswith("////"): - continue - - line = line.rstrip() - strline = line.lstrip('/ \t') - - if ':' in strline: - pre, post = strline.split(':', 1) - pre = pre.rstrip() - if pre == "Description": - strline = post.lstrip() - elif pre in ("Class", "Access", "Function", "Created by", "Enum"): - continue - - if strline or len(newlines) > 0: - newlines.append('/// ' + strline) - - #if reading_desc: - # newlines.append('/// ' + line[min(indent, len(line) - len(strline)):]) - #else: - # # A "Description:" text starts the description. - # if strline.startswith("Description"): - # strline = strline[11:].lstrip(': \t') - # indent = len(line) - len(strline) - # reading_desc = True - # newlines.append('/// ' + strline) - # else: - # print line - - newcode = '\n'.join(newlines) - if len(newcode) > 0: - return newcode - else: - return "" + return code def translateFunctionName(name): if name.startswith("__"): @@ -139,11 +127,17 @@ def translated_type_name(type, scoped=True): return "object" elif typename == "PN_stdfloat": return "float" + elif typename == "size_t": + return "int" if interrogate_type_is_atomic(type): token = interrogate_type_atomic_token(type) if token == 7: return 'str' + elif token == 8: + return 'long' + elif token == 9: + return 'NoneType' else: return typename @@ -156,12 +150,25 @@ def translated_type_name(type, scoped=True): else: return typename + def processElement(handle, element): if interrogate_element_has_comment(element): print(comment(interrogate_element_comment(element)), file=handle) + elif interrogate_element_has_getter(element): + # If the property has no comment, use the comment of the getter. + getter = interrogate_element_getter(element) + if interrogate_function_has_comment(getter): + print(block_comment(interrogate_function_comment(getter)), file=handle) + + if interrogate_element_is_mapping(element) or \ + interrogate_element_is_sequence(element): + suffix = "[]" + else: + suffix = "" print(translated_type_name(interrogate_element_type(element)), end=' ', file=handle) - print(interrogate_element_name(element) + ';', file=handle) + print(interrogate_element_name(element) + suffix + ';', file=handle) + def processFunction(handle, function, isConstructor = False): for i_wrapper in range(interrogate_function_number_of_python_wrappers(function)): @@ -195,6 +202,7 @@ def processFunction(handle, function, isConstructor = False): print(");", file=handle) + def processType(handle, type): typename = translated_type_name(type, scoped=False) derivations = [ translated_type_name(interrogate_type_get_derivation(type, n)) for n in range(interrogate_type_number_of_derivations(type)) ] @@ -211,8 +219,10 @@ def processType(handle, type): print(interrogate_type_enum_value_name(type, i_value), "=", interrogate_type_enum_value(type, i_value), ",", file=handle) elif interrogate_type_is_typedef(type): - wrapped_type = translated_type_name(interrogate_type_wrapped_type(type)) - print("typedef %s %s;" % (wrapped_type, typename), file=handle) + wrapped_type = interrogate_type_wrapped_type(type) + if interrogate_type_is_global(wrapped_type): + wrapped_type_name = translated_type_name(wrapped_type) + print("typedef %s %s;" % (wrapped_type_name, typename), file=handle) return else: if interrogate_type_is_struct(type): @@ -249,6 +259,7 @@ def processType(handle, type): print("};", file=handle) def processModule(handle, package): + print("Processing module %s" % (package)) print("namespace %s {" % package, file=handle) if package != "core": @@ -280,22 +291,39 @@ def processModule(handle, package): if __name__ == "__main__": handle = open("pandadoc.hpp", "w") + mainpage = MAINPAGE.strip() + if mainpage: + print("/**\n * " + mainpage.replace('\n', '\n * ') + '\n */', file=handle) + print(comment("Panda3D modules that are implemented in C++."), file=handle) print("namespace panda3d {", file=handle) # Determine the path to the interrogatedb files - interrogate_add_search_directory(os.path.join(os.path.dirname(pandac.__file__), "..", "..", "etc")) - interrogate_add_search_directory(os.path.join(os.path.dirname(pandac.__file__), "input")) + pandac = os.path.dirname(pandac.__file__) + interrogate_add_search_directory(os.path.join(pandac, "..", "..", "etc")) + interrogate_add_search_directory(os.path.join(pandac, "input")) import panda3d.core processModule(handle, "core") + # Determine the suffix for the extension modules. + if sys.version_info >= (3, 0): + import _imp + ext_suffix = _imp.extension_suffixes()[0] + elif sys.platform == "win32": + ext_suffix = ".pyd" + else: + ext_suffix = ".so" + for lib in os.listdir(os.path.dirname(panda3d.__file__)): - if lib.endswith(('.pyd', '.so')) and not lib.startswith('core.'): - module_name = os.path.splitext(lib)[0] + if lib.endswith(ext_suffix) and not lib.startswith('core.'): + module_name = lib[:-len(ext_suffix)] __import__("panda3d." + module_name) processModule(handle, module_name) - print("}", file=handle) handle.close() + + print("Wrote output to pandadoc.hpp. You can now run:") + print() + print(" doxygen built/direct/directscripts/Doxyfile.python") From d28e99db82660e2eef8ab8c531ca129b5fa37e6c Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 16:14:28 +0100 Subject: [PATCH 363/424] directscripts: some fixes for C++ API reference --- direct/src/directscripts/Doxyfile.cxx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/direct/src/directscripts/Doxyfile.cxx b/direct/src/directscripts/Doxyfile.cxx index 8b4fda2230..7aec9a9d3d 100644 --- a/direct/src/directscripts/Doxyfile.cxx +++ b/direct/src/directscripts/Doxyfile.cxx @@ -797,7 +797,15 @@ EXCLUDE = dtool/src/parser-inc \ panda/src/linmath/fltnames.h \ panda/src/linmath/dblnames.h \ panda/src/linmath/dbl2fltnames.h \ - panda/src/linmath/flt2dblnames.h + panda/src/linmath/flt2dblnames.h \ + panda/src/android \ + panda/src/iphone \ + panda/src/tinydisplay \ + panda/src/movies/dr_flac.h \ + dtool/src/dtoolbase/pdtoa.cxx \ + dtool/src/dtoolutil/panda_getopt_long.h \ + dtool/src/dtoolutil/panda_getopt_impl.h \ + dtool/src/dtoolutil/panda_getopt_impl.cxx # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1972,7 +1980,13 @@ PREDEFINED = TVOLATILE= \ EXTEND= \ ALLOC_DELETED_CHAIN(x)= \ BLOCKING= \ - TYPENAME=typename + TYPENAME=typename \ + MAKE_PROPERTY(x)= \ + MAKE_PROPERTY2(x)= \ + MAKE_SEQ(x)= \ + MAKE_SEQ_PROPERTY(x)= \ + MAKE_MAP_PROPERTY(x)= \ + MAKE_MAP_KEYS_SEQ(x)= # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The From 729a2364bcff2ebdee3b271657f0f5fca5aaac72 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 16:25:29 +0100 Subject: [PATCH 364/424] windisplay: remove unused WHQL stuff --- panda/src/windisplay/winDetectDx.h | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/panda/src/windisplay/winDetectDx.h b/panda/src/windisplay/winDetectDx.h index df7d89f0ff..4b979de6f0 100644 --- a/panda/src/windisplay/winDetectDx.h +++ b/panda/src/windisplay/winDetectDx.h @@ -21,18 +21,6 @@ typedef struct { } DISPLAY_FORMAT; -typedef union -{ - struct - { - unsigned int day : 8; - unsigned int month : 8; - unsigned int year : 16; - }; - DWORD whql; -} -WHQL; - static DISPLAY_FORMAT display_format_array [ ] = { D3DFMT_X8R8G8B8, 32, FALSE, D3DFMT_R5G6B5, 16, FALSE, @@ -321,14 +309,6 @@ static int get_display_information (DisplaySearchParameters &display_search_para if (debug) { printf ("DRIVER VERSION: %d.%d.%d.%d \n", product, version, sub_version, build); } - - WHQL whql; - - whql.whql= d3d_adapter_identifier.WHQLLevel; - - if (debug) { - printf ("WHQL: %d %d %d \n", whql.day, whql.day, whql.year); - } } if (direct_3d -> GetDeviceCaps (adapter, device_type, &d3d_caps) == D3D_OK) { From b40ecd6c51bdc569f1bfd1ea6af55f9a04da8554 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 16:27:08 +0100 Subject: [PATCH 365/424] general: fix some old-style docstring comment blocks --- panda/src/dxml/config_dxml.cxx | 47 +++++++++++++++------------------- panda/src/pgraph/camera.cxx | 11 +++----- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/panda/src/dxml/config_dxml.cxx b/panda/src/dxml/config_dxml.cxx index bbc119cb14..8cb75706f9 100644 --- a/panda/src/dxml/config_dxml.cxx +++ b/panda/src/dxml/config_dxml.cxx @@ -30,14 +30,12 @@ ConfigureFn(config_dxml) { init_libdxml(); } -//////////////////////////////////////////////////////////////////// -// Function: init_libdxml -// Description: Initializes the library. This must be called at -// least once before any of the functions or classes in -// this library can be used. Normally it will be -// called by the static initializers and need not be -// called explicitly, but special cases exist. -//////////////////////////////////////////////////////////////////// +/** + * Initializes the library. This must be called at least once before any of + * the functions or classes in this library can be used. Normally it will be + * called by the static initializers and need not be called explicitly, but + * special cases exist. + */ void init_libdxml() { static bool initialized = false; @@ -48,11 +46,10 @@ init_libdxml() { } BEGIN_PUBLISH -//////////////////////////////////////////////////////////////////// -// Function: read_xml_stream -// Description: Reads an XML document from the indicated stream. -// Returns the document, or NULL on error. -//////////////////////////////////////////////////////////////////// +/** + * Reads an XML document from the indicated stream. + * @returns the document, or NULL on error. + */ TiXmlDocument * read_xml_stream(std::istream &in) { TiXmlDocument *doc = new TiXmlDocument; @@ -67,10 +64,9 @@ read_xml_stream(std::istream &in) { END_PUBLISH BEGIN_PUBLISH -//////////////////////////////////////////////////////////////////// -// Function: write_xml_stream -// Description: Writes an XML document to the indicated stream. -//////////////////////////////////////////////////////////////////// +/** + * Writes an XML document to the indicated stream. + */ void write_xml_stream(std::ostream &out, TiXmlDocument *doc) { out << *doc; @@ -78,10 +74,9 @@ write_xml_stream(std::ostream &out, TiXmlDocument *doc) { END_PUBLISH BEGIN_PUBLISH -//////////////////////////////////////////////////////////////////// -// Function: print_xml -// Description: Writes an XML object to stdout, with formatting. -//////////////////////////////////////////////////////////////////// +/** + * Writes an XML object to stdout, with formatting. + */ void print_xml(TiXmlNode *xnode) { xnode->Print(stdout, 0); @@ -89,12 +84,10 @@ print_xml(TiXmlNode *xnode) { END_PUBLISH BEGIN_PUBLISH -//////////////////////////////////////////////////////////////////// -// Function: print_xml_to_file -// Description: Writes an XML object to the indicated file, with -// formatting. Unfortunately the VFS cannot be -// supported; the file must be a real filename on disk. -//////////////////////////////////////////////////////////////////// +/** + * Writes an XML object to the indicated file, with formatting. Unfortunately + * the VFS cannot be supported; the file must be a real filename on disk. + */ void print_xml_to_file(const Filename &filename, TiXmlNode *xnode) { std::string os_name = filename.to_os_specific(); diff --git a/panda/src/pgraph/camera.cxx b/panda/src/pgraph/camera.cxx index 5a10d6145e..ddf8006862 100644 --- a/panda/src/pgraph/camera.cxx +++ b/panda/src/pgraph/camera.cxx @@ -280,13 +280,10 @@ write_datagram(BamWriter *manager, Datagram &dg) { } } -//////////////////////////////////////////////////////////////////// -// Function: Camera::complete_pointers -// Access: Public, Virtual -// Description: Receives an array of pointers, one for each time -// manager->read_pointer() was called in fillin(). -// Returns the number of pointers processed. -//////////////////////////////////////////////////////////////////// +/** + * Receives an array of pointers, one for each time manager->read_pointer() + * was called in fillin(). Returns the number of pointers processed. + */ int Camera:: complete_pointers(TypedWritable **p_list, BamReader *manager) { int pi = LensNode::complete_pointers(p_list, manager); From bd6ef2b0ea2832ff1095f9e41b3137452f0619f4 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 19:44:53 +0100 Subject: [PATCH 366/424] Rename CollisionTube to CollisionCapsule (bam 6.44) Adds ability to register "obsolete" names for certain types under older .bam versions, so that we are still able to write out old .bam files. Fixes #347 --- makepanda/makepanda.vcproj | 6 +- panda/src/bullet/bulletBodyNode.cxx | 12 +- panda/src/bullet/bulletCapsuleShape.cxx | 10 +- panda/src/bullet/bulletCapsuleShape.h | 4 +- panda/src/collide/collisionBox.cxx | 20 +-- panda/src/collide/collisionBox.h | 2 +- .../{collisionTube.I => collisionCapsule.I} | 38 ++--- ...collisionTube.cxx => collisionCapsule.cxx} | 148 +++++++++--------- .../{collisionTube.h => collisionCapsule.h} | 42 +++-- .../collide/collisionHandlerFluidPusher.cxx | 2 +- panda/src/collide/collisionPlane.cxx | 22 +-- panda/src/collide/collisionPlane.h | 2 +- panda/src/collide/collisionSolid.cxx | 8 +- panda/src/collide/collisionSolid.h | 4 +- panda/src/collide/collisionSphere.cxx | 20 +-- panda/src/collide/collisionSphere.h | 2 +- panda/src/collide/collisionTraverser.cxx | 4 +- panda/src/collide/config_collide.cxx | 10 +- panda/src/collide/p3collide_composite1.cxx | 3 +- panda/src/collide/p3collide_composite2.cxx | 3 +- panda/src/display/graphicsEngine.cxx | 8 +- panda/src/display/graphicsEngine.h | 4 +- panda/src/doc/collisionFlags.txt | 2 +- panda/src/egg/eggGroup.cxx | 3 +- panda/src/egg2pg/eggLoader.cxx | 20 +-- panda/src/egg2pg/eggLoader.h | 4 +- panda/src/egg2pg/eggSaver.cxx | 30 ++-- panda/src/putil/bam.h | 3 +- panda/src/putil/bamWriter.cxx | 67 +++++++- panda/src/putil/bamWriter.h | 4 + pandatool/src/eggprogs/eggMakeTube.cxx | 2 +- pandatool/src/eggprogs/eggMakeTube.h | 2 +- tests/collide/test_collision_tube.py | 63 ++++++++ 33 files changed, 360 insertions(+), 214 deletions(-) rename panda/src/collide/{collisionTube.I => collisionCapsule.I} (70%) rename panda/src/collide/{collisionTube.cxx => collisionCapsule.cxx} (89%) rename panda/src/collide/{collisionTube.h => collisionCapsule.h} (81%) create mode 100644 tests/collide/test_collision_tube.py diff --git a/makepanda/makepanda.vcproj b/makepanda/makepanda.vcproj index ad7f219650..03324fb60e 100644 --- a/makepanda/makepanda.vcproj +++ b/makepanda/makepanda.vcproj @@ -1043,7 +1043,7 @@ - + @@ -1054,7 +1054,7 @@ - + @@ -1089,7 +1089,7 @@ - + diff --git a/panda/src/bullet/bulletBodyNode.cxx b/panda/src/bullet/bulletBodyNode.cxx index 81a056adc2..cb9d7ed9c0 100644 --- a/panda/src/bullet/bulletBodyNode.cxx +++ b/panda/src/bullet/bulletBodyNode.cxx @@ -28,7 +28,7 @@ #include "collisionPlane.h" #include "collisionSphere.h" #include "collisionPolygon.h" -#include "collisionTube.h" +#include "collisionCapsule.h" TypeHandle BulletBodyNode::_type_handle; @@ -813,12 +813,12 @@ add_shapes_from_collision_solids(CollisionNode *cnode) { do_add_shape(BulletBoxShape::make_from_solid(box), ts); } - // CollisionTube - else if (CollisionTube::get_class_type() == type) { - CPT(CollisionTube) tube = DCAST(CollisionTube, solid); - CPT(TransformState) ts = TransformState::make_pos((tube->get_point_b() + tube->get_point_a()) / 2.0); + // CollisionCapsule + else if (CollisionCapsule::get_class_type() == type) { + CPT(CollisionCapsule) capsule = DCAST(CollisionCapsule, solid); + CPT(TransformState) ts = TransformState::make_pos((capsule->get_point_b() + capsule->get_point_a()) / 2.0); - do_add_shape(BulletCapsuleShape::make_from_solid(tube), ts); + do_add_shape(BulletCapsuleShape::make_from_solid(capsule), ts); } // CollisionPlane diff --git a/panda/src/bullet/bulletCapsuleShape.cxx b/panda/src/bullet/bulletCapsuleShape.cxx index 0bdb1284dc..cde5e64050 100644 --- a/panda/src/bullet/bulletCapsuleShape.cxx +++ b/panda/src/bullet/bulletCapsuleShape.cxx @@ -87,16 +87,16 @@ ptr() const { /** * Constructs a new BulletCapsuleShape using the information from a - * CollisionTube from the builtin collision system. + * CollisionCapsule from the builtin collision system. */ BulletCapsuleShape *BulletCapsuleShape:: -make_from_solid(const CollisionTube *solid) { - +make_from_solid(const CollisionCapsule *solid) { + PN_stdfloat radius = solid->get_radius(); - // Get tube's cylinder height: length from point A to point B + // Get capsule'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. + // CollisionCapsules are always Z-Up. return new BulletCapsuleShape(radius, height, Z_up); } diff --git a/panda/src/bullet/bulletCapsuleShape.h b/panda/src/bullet/bulletCapsuleShape.h index f8eecfe3d7..e78b06bc08 100644 --- a/panda/src/bullet/bulletCapsuleShape.h +++ b/panda/src/bullet/bulletCapsuleShape.h @@ -20,7 +20,7 @@ #include "bullet_utils.h" #include "bulletShape.h" -#include "collisionTube.h" +#include "collisionCapsule.h" /** * @@ -35,7 +35,7 @@ PUBLISHED: BulletCapsuleShape(const BulletCapsuleShape ©); INLINE ~BulletCapsuleShape(); - static BulletCapsuleShape *make_from_solid(const CollisionTube *solid); + static BulletCapsuleShape *make_from_solid(const CollisionCapsule *solid); INLINE PN_stdfloat get_radius() const; INLINE PN_stdfloat get_half_height() const; diff --git a/panda/src/collide/collisionBox.cxx b/panda/src/collide/collisionBox.cxx index a8af257456..8528e7bc27 100644 --- a/panda/src/collide/collisionBox.cxx +++ b/panda/src/collide/collisionBox.cxx @@ -16,7 +16,7 @@ #include "collisionRay.h" #include "collisionSphere.h" #include "collisionSegment.h" -#include "collisionTube.h" +#include "collisionCapsule.h" #include "collisionHandler.h" #include "collisionEntry.h" #include "config_collide.h" @@ -547,19 +547,19 @@ test_intersection_from_segment(const CollisionEntry &entry) const { } /** - * Double dispatch point for tube as a FROM object + * Double dispatch point for capsule as a FROM object */ PT(CollisionEntry) CollisionBox:: -test_intersection_from_tube(const CollisionEntry &entry) const { - const CollisionTube *tube; - DCAST_INTO_R(tube, entry.get_from(), nullptr); +test_intersection_from_capsule(const CollisionEntry &entry) const { + const CollisionCapsule *capsule; + DCAST_INTO_R(capsule, entry.get_from(), nullptr); const LMatrix4 &wrt_mat = entry.get_wrt_mat(); - LPoint3 from_a = tube->get_point_a() * wrt_mat; - LPoint3 from_b = tube->get_point_b() * wrt_mat; + LPoint3 from_a = capsule->get_point_a() * wrt_mat; + LPoint3 from_b = capsule->get_point_b() * wrt_mat; LVector3 from_direction = from_b - from_a; - PN_stdfloat radius_sq = wrt_mat.xform_vec(LVector3(0, 0, tube->get_radius())).length_squared(); + PN_stdfloat radius_sq = wrt_mat.xform_vec(LVector3(0, 0, capsule->get_radius())).length_squared(); PN_stdfloat radius = csqrt(radius_sq); LPoint3 box_min = get_min(); @@ -620,7 +620,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const { LVector3 delta(0); delta[edges[i].axis] = dimensions[edges[i].axis]; double u1, u2; - CollisionTube::calc_closest_segment_points(u1, u2, from_a, from_direction, vertex, delta); + CollisionCapsule::calc_closest_segment_points(u1, u2, from_a, from_direction, vertex, delta); PN_stdfloat dist_sq = ((from_a + from_direction * u1) - (vertex + delta * u2)).length_squared(); if (dist_sq < best_dist_sq) { best_dist_sq = dist_sq; @@ -682,7 +682,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const { new_entry->set_interior_point(point - interior_vec * radius); new_entry->set_surface_point(surface_point); - if (has_effective_normal() && tube->get_respect_effective_normal()) { + if (has_effective_normal() && capsule->get_respect_effective_normal()) { new_entry->set_surface_normal(get_effective_normal()); } else { new_entry->set_surface_normal(normal); diff --git a/panda/src/collide/collisionBox.h b/panda/src/collide/collisionBox.h index cf9e8bc15b..db9d2c2aa9 100644 --- a/panda/src/collide/collisionBox.h +++ b/panda/src/collide/collisionBox.h @@ -82,7 +82,7 @@ protected: virtual PT(CollisionEntry) test_intersection_from_segment(const CollisionEntry &entry) const; virtual PT(CollisionEntry) - test_intersection_from_tube(const CollisionEntry &entry) const; + test_intersection_from_capsule(const CollisionEntry &entry) const; virtual PT(CollisionEntry) test_intersection_from_box(const CollisionEntry &entry) const; diff --git a/panda/src/collide/collisionTube.I b/panda/src/collide/collisionCapsule.I similarity index 70% rename from panda/src/collide/collisionTube.I rename to panda/src/collide/collisionCapsule.I index 53e0f4cff6..78b8c3559b 100644 --- a/panda/src/collide/collisionTube.I +++ b/panda/src/collide/collisionCapsule.I @@ -6,7 +6,7 @@ * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * - * @file collisionTube.I + * @file collisionCapsule.I * @author drose * @date 2003-09-25 */ @@ -14,8 +14,8 @@ /** * */ -INLINE CollisionTube:: -CollisionTube(const LPoint3 &a, const LPoint3 &b, PN_stdfloat radius) : +INLINE CollisionCapsule:: +CollisionCapsule(const LPoint3 &a, const LPoint3 &b, PN_stdfloat radius) : _a(a), _b(b), _radius(radius) { recalc_internals(); @@ -25,8 +25,8 @@ CollisionTube(const LPoint3 &a, const LPoint3 &b, PN_stdfloat radius) : /** * */ -INLINE CollisionTube:: -CollisionTube(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az, +INLINE CollisionCapsule:: +CollisionCapsule(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az, PN_stdfloat bx, PN_stdfloat by, PN_stdfloat bz, PN_stdfloat radius) : _a(ax, ay, az), _b(bx, by, bz), _radius(radius) @@ -36,17 +36,17 @@ CollisionTube(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az, } /** - * Creates an invalid tube. Only used when reading from a bam file. + * Creates an invalid capsule. Only used when reading from a bam file. */ -INLINE CollisionTube:: -CollisionTube() { +INLINE CollisionCapsule:: +CollisionCapsule() { } /** * */ -INLINE CollisionTube:: -CollisionTube(const CollisionTube ©) : +INLINE CollisionCapsule:: +CollisionCapsule(const CollisionCapsule ©) : CollisionSolid(copy), _a(copy._a), _b(copy._b), @@ -58,7 +58,7 @@ CollisionTube(const CollisionTube ©) : /** * Flushes the PStatCollectors used during traversal. */ -INLINE void CollisionTube:: +INLINE void CollisionCapsule:: flush_level() { _volume_pcollector.flush_level(); _test_pcollector.flush_level(); @@ -67,7 +67,7 @@ flush_level() { /** * */ -INLINE void CollisionTube:: +INLINE void CollisionCapsule:: set_point_a(const LPoint3 &a) { _a = a; recalc_internals(); @@ -76,7 +76,7 @@ set_point_a(const LPoint3 &a) { /** * */ -INLINE void CollisionTube:: +INLINE void CollisionCapsule:: set_point_a(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z) { set_point_a(LPoint3(x, y, z)); } @@ -84,7 +84,7 @@ set_point_a(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z) { /** * */ -INLINE const LPoint3 &CollisionTube:: +INLINE const LPoint3 &CollisionCapsule:: get_point_a() const { return _a; } @@ -92,7 +92,7 @@ get_point_a() const { /** * */ -INLINE void CollisionTube:: +INLINE void CollisionCapsule:: set_point_b(const LPoint3 &b) { _b = b; recalc_internals(); @@ -101,7 +101,7 @@ set_point_b(const LPoint3 &b) { /** * */ -INLINE void CollisionTube:: +INLINE void CollisionCapsule:: set_point_b(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z) { set_point_b(LPoint3(x, y, z)); } @@ -109,7 +109,7 @@ set_point_b(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z) { /** * */ -INLINE const LPoint3 &CollisionTube:: +INLINE const LPoint3 &CollisionCapsule:: get_point_b() const { return _b; } @@ -117,7 +117,7 @@ get_point_b() const { /** * */ -INLINE void CollisionTube:: +INLINE void CollisionCapsule:: set_radius(PN_stdfloat radius) { nassertv(radius >= 0.0f); _radius = radius; @@ -131,7 +131,7 @@ set_radius(PN_stdfloat radius) { /** * */ -INLINE PN_stdfloat CollisionTube:: +INLINE PN_stdfloat CollisionCapsule:: get_radius() const { return _radius; } diff --git a/panda/src/collide/collisionTube.cxx b/panda/src/collide/collisionCapsule.cxx similarity index 89% rename from panda/src/collide/collisionTube.cxx rename to panda/src/collide/collisionCapsule.cxx index 90f82a3630..91ab319735 100644 --- a/panda/src/collide/collisionTube.cxx +++ b/panda/src/collide/collisionCapsule.cxx @@ -6,12 +6,12 @@ * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * - * @file collisionTube.cxx + * @file collisionCapsule.cxx * @author drose * @date 2003-09-25 */ -#include "collisionTube.h" +#include "collisionCapsule.h" #include "collisionSphere.h" #include "collisionLine.h" #include "collisionRay.h" @@ -35,30 +35,30 @@ #include "geomVertexWriter.h" #include "boundingSphere.h" -PStatCollector CollisionTube::_volume_pcollector("Collision Volumes:CollisionTube"); -PStatCollector CollisionTube::_test_pcollector("Collision Tests:CollisionTube"); -TypeHandle CollisionTube::_type_handle; +PStatCollector CollisionCapsule::_volume_pcollector("Collision Volumes:CollisionCapsule"); +PStatCollector CollisionCapsule::_test_pcollector("Collision Tests:CollisionCapsule"); +TypeHandle CollisionCapsule::_type_handle; /** * */ -CollisionSolid *CollisionTube:: +CollisionSolid *CollisionCapsule:: make_copy() { - return new CollisionTube(*this); + return new CollisionCapsule(*this); } /** * */ -PT(CollisionEntry) CollisionTube:: +PT(CollisionEntry) CollisionCapsule:: test_intersection(const CollisionEntry &entry) const { - return entry.get_into()->test_intersection_from_tube(entry); + return entry.get_into()->test_intersection_from_capsule(entry); } /** * Transforms the solid by the indicated matrix. */ -void CollisionTube:: +void CollisionCapsule:: xform(const LMatrix4 &mat) { _a = _a * mat; _b = _b * mat; @@ -77,7 +77,7 @@ xform(const LMatrix4 &mat) { * collision purposes. The closest intersection point to this origin point is * considered to be the most significant. */ -LPoint3 CollisionTube:: +LPoint3 CollisionCapsule:: get_collision_origin() const { return get_point_a(); } @@ -86,7 +86,7 @@ get_collision_origin() const { * Returns a PStatCollector that is used to count the number of bounding * volume tests made against a solid of this type in a given frame. */ -PStatCollector &CollisionTube:: +PStatCollector &CollisionCapsule:: get_volume_pcollector() { return _volume_pcollector; } @@ -95,7 +95,7 @@ get_volume_pcollector() { * Returns a PStatCollector that is used to count the number of intersection * tests made against a solid of this type in a given frame. */ -PStatCollector &CollisionTube:: +PStatCollector &CollisionCapsule:: get_test_pcollector() { return _test_pcollector; } @@ -103,15 +103,15 @@ get_test_pcollector() { /** * */ -void CollisionTube:: +void CollisionCapsule:: output(std::ostream &out) const { - out << "tube, a (" << _a << "), b (" << _b << "), r " << _radius; + out << "capsule, a (" << _a << "), b (" << _b << "), r " << _radius; } /** * */ -PT(BoundingVolume) CollisionTube:: +PT(BoundingVolume) CollisionCapsule:: compute_internal_bounds() const { PT(BoundingVolume) bound = CollisionSolid::compute_internal_bounds(); @@ -143,7 +143,7 @@ compute_internal_bounds() const { /** * */ -PT(CollisionEntry) CollisionTube:: +PT(CollisionEntry) CollisionCapsule:: test_intersection_from_sphere(const CollisionEntry &entry) const { const CollisionSphere *sphere; DCAST_INTO_R(sphere, entry.get_from(), nullptr); @@ -160,7 +160,7 @@ test_intersection_from_sphere(const CollisionEntry &entry) const { PN_stdfloat actual_t = 0.0f; if (wrt_prev_space != wrt_space) { - // If the sphere is moving relative to the tube, it becomes a tube itself. + // If the sphere is moving relative to the capsule, it becomes a capsule itself. from_a = sphere->get_center() * wrt_prev_space->get_mat(); } @@ -195,11 +195,11 @@ test_intersection_from_sphere(const CollisionEntry &entry) const { LPoint3 into_intersection_point; if (t2 > 1.0) { - // Point b is within the tube. The first intersection point is point b + // Point b is within the capsule. The first intersection point is point b // itself. into_intersection_point = from_b; } else { - // Point b is outside the tube, and point a is either inside the tube or + // Point b is outside the capsule, and point a is either inside the capsule or // beyond it. The first intersection point is at t2. into_intersection_point = from_a + t2 * from_direction; } @@ -221,7 +221,7 @@ test_intersection_from_sphere(const CollisionEntry &entry) const { /** * */ -PT(CollisionEntry) CollisionTube:: +PT(CollisionEntry) CollisionCapsule:: test_intersection_from_line(const CollisionEntry &entry) const { const CollisionLine *line; DCAST_INTO_R(line, entry.get_from(), nullptr); @@ -269,7 +269,7 @@ test_intersection_from_line(const CollisionEntry &entry) const { /** * */ -PT(CollisionEntry) CollisionTube:: +PT(CollisionEntry) CollisionCapsule:: test_intersection_from_ray(const CollisionEntry &entry) const { const CollisionRay *ray; DCAST_INTO_R(ray, entry.get_from(), nullptr); @@ -299,11 +299,11 @@ test_intersection_from_ray(const CollisionEntry &entry) const { LPoint3 into_intersection_point; if (t1 < 0.0) { - // Point a is within the tube. The first intersection point is point a + // Point a is within the capsule. The first intersection point is point a // itself. into_intersection_point = from_origin; } else { - // Point a is outside the tube. The first intersection point is at t1. + // Point a is outside the capsule. The first intersection point is at t1. into_intersection_point = from_origin + t1 * from_direction; } set_intersection_point(new_entry, into_intersection_point, 0.0); @@ -330,7 +330,7 @@ test_intersection_from_ray(const CollisionEntry &entry) const { /** * */ -PT(CollisionEntry) CollisionTube:: +PT(CollisionEntry) CollisionCapsule:: test_intersection_from_segment(const CollisionEntry &entry) const { const CollisionSegment *segment; DCAST_INTO_R(segment, entry.get_from(), nullptr); @@ -362,11 +362,11 @@ test_intersection_from_segment(const CollisionEntry &entry) const { LPoint3 into_intersection_point; if (t1 < 0.0) { - // Point a is within the tube. The first intersection point is point a + // Point a is within the capsule. The first intersection point is point a // itself. into_intersection_point = from_a; } else { - // Point a is outside the tube, and point b is either inside the tube or + // Point a is outside the capsule, and point b is either inside the capsule or // beyond it. The first intersection point is at t1. into_intersection_point = from_a + t1 * from_direction; } @@ -394,22 +394,22 @@ test_intersection_from_segment(const CollisionEntry &entry) const { /** * */ -PT(CollisionEntry) CollisionTube:: -test_intersection_from_tube(const CollisionEntry &entry) const { - const CollisionTube *tube; - DCAST_INTO_R(tube, entry.get_from(), nullptr); +PT(CollisionEntry) CollisionCapsule:: +test_intersection_from_capsule(const CollisionEntry &entry) const { + const CollisionCapsule *capsule; + DCAST_INTO_R(capsule, entry.get_from(), nullptr); LPoint3 into_a = _a; LVector3 into_direction = _b - into_a; const LMatrix4 &wrt_mat = entry.get_wrt_mat(); - LPoint3 from_a = tube->get_point_a() * wrt_mat; - LPoint3 from_b = tube->get_point_b() * wrt_mat; + LPoint3 from_a = capsule->get_point_a() * wrt_mat; + LPoint3 from_b = capsule->get_point_b() * wrt_mat; LVector3 from_direction = from_b - from_a; LVector3 from_radius_v = - LVector3(tube->get_radius(), 0.0f, 0.0f) * wrt_mat; + LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat; PN_stdfloat from_radius = length(from_radius_v); // Determine the points on each segment with the smallest distance between. @@ -420,7 +420,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const { LPoint3 into_closest = into_a + into_direction * into_t; LPoint3 from_closest = from_a + from_direction * from_t; - // If the distance is greater than the sum of tube radii, the test fails. + // If the distance is greater than the sum of capsule radii, the test fails. LVector3 closest_vec = from_closest - into_closest; PN_stdfloat distance = closest_vec.length(); if (distance > _radius + from_radius) { @@ -442,7 +442,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const { new_entry->set_surface_point(into_closest + surface_normal * _radius); new_entry->set_interior_point(from_closest - surface_normal * from_radius); - if (has_effective_normal() && tube->get_respect_effective_normal()) { + if (has_effective_normal() && capsule->get_respect_effective_normal()) { new_entry->set_surface_normal(get_effective_normal()); } else if (distance != 0) { new_entry->set_surface_normal(surface_normal); @@ -458,7 +458,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const { /** * */ -PT(CollisionEntry) CollisionTube:: +PT(CollisionEntry) CollisionCapsule:: test_intersection_from_parabola(const CollisionEntry &entry) const { const CollisionParabola *parabola; DCAST_INTO_R(parabola, entry.get_from(), nullptr); @@ -510,14 +510,14 @@ test_intersection_from_parabola(const CollisionEntry &entry) const { * Fills the _viz_geom GeomNode up with Geoms suitable for rendering this * solid. */ -void CollisionTube:: +void CollisionCapsule:: fill_viz_geom() { if (collide_cat.is_debug()) { collide_cat.debug() << "Recomputing viz for " << *this << "\n"; } - // Generate the vertices such that we draw a tube with one endpoint at (0, + // Generate the vertices such that we draw a capsule with one endpoint at (0, // 0, 0), and another at (0, length, 0). Then we'll rotate and translate it // into place with the appropriate look_at matrix. LVector3 direction = (_b - _a); @@ -576,9 +576,9 @@ fill_viz_geom() { /** * Should be called internally to recompute the matrix and length when the - * properties of the tube have changed. + * properties of the capsule have changed. */ -void CollisionTube:: +void CollisionCapsule:: recalc_internals() { LVector3 direction = (_b - _a); _length = direction.length(); @@ -595,7 +595,7 @@ recalc_internals() { * Calculates a particular vertex on the surface of the first endcap * hemisphere, for use in generating the viz geometry. */ -LVertex CollisionTube:: +LVertex CollisionCapsule:: calc_sphere1_vertex(int ri, int si, int num_rings, int num_slices) { PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings; PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices; @@ -620,7 +620,7 @@ calc_sphere1_vertex(int ri, int si, int num_rings, int num_slices) { * Calculates a particular vertex on the surface of the second endcap * hemisphere, for use in generating the viz geometry. */ -LVertex CollisionTube:: +LVertex CollisionCapsule:: calc_sphere2_vertex(int ri, int si, int num_rings, int num_slices, PN_stdfloat length) { PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings; @@ -646,7 +646,7 @@ calc_sphere2_vertex(int ri, int si, int num_rings, int num_slices, * Given line segments s1 and s2 defined by two points each, computes the * point on each segment with the closest distance between them. */ -void CollisionTube:: +void CollisionCapsule:: calc_closest_segment_points(double &t1, double &t2, const LPoint3 &from1, const LVector3 &delta1, const LPoint3 &from2, const LVector3 &delta2) { @@ -717,18 +717,18 @@ calc_closest_segment_points(double &t1, double &t2, } /** - * Determine the point(s) of intersection of a parametric line with the tube. + * Determine the point(s) of intersection of a parametric line with the capsule. * The line is infinite in both directions, and passes through "from" and - * from+delta. If the line does not intersect the tube, the function returns - * false, and t1 and t2 are undefined. If it does intersect the tube, it + * from+delta. If the line does not intersect the capsule, the function returns + * false, and t1 and t2 are undefined. If it does intersect the capsule, it * returns true, and t1 and t2 are set to the points along the equation * from+t*delta that correspond to the two points of intersection. */ -bool CollisionTube:: +bool CollisionCapsule:: intersects_line(double &t1, double &t2, const LPoint3 &from0, const LVector3 &delta0, PN_stdfloat inflate_radius) const { - // Convert the line into our canonical coordinate space: the tube is aligned + // Convert the line into our canonical coordinate space: the capsule is aligned // with the y axis. LPoint3 from = from0 * _inv_mat; LVector3 delta = delta0 * _inv_mat; @@ -776,7 +776,7 @@ intersects_line(double &t1, double &t2, } } - // The point is within the tube! + // The point is within the capsule! t1 = t2 = 0.0; return true; } @@ -819,15 +819,15 @@ intersects_line(double &t1, double &t2, PN_stdfloat t2_y = from[1] + t2 * delta[1]; if (t1_y < -radius && t2_y < -radius) { - // Both points are way off the bottom of the tube; no intersection. + // Both points are way off the bottom of the capsule; no intersection. return false; } else if (t1_y > _length + radius && t2_y > _length + radius) { - // Both points are way off the top of the tube; no intersection. + // Both points are way off the top of the capsule; no intersection. return false; } if (t1_y < 0.0f) { - // The starting point is off the bottom of the tube. Test the line + // The starting point is off the bottom of the capsule. Test the line // against the first endcap. double t1a, t2a; if (!sphere_intersects_line(t1a, t2a, 0.0f, from, delta, radius)) { @@ -838,7 +838,7 @@ intersects_line(double &t1, double &t2, t1 = t1a; } else if (t1_y > _length) { - // The starting point is off the top of the tube. Test the line against + // The starting point is off the top of the capsule. Test the line against // the second endcap. double t1b, t2b; if (!sphere_intersects_line(t1b, t2b, _length, from, delta, radius)) { @@ -850,7 +850,7 @@ intersects_line(double &t1, double &t2, } if (t2_y < 0.0f) { - // The ending point is off the bottom of the tube. Test the line against + // The ending point is off the bottom of the capsule. Test the line against // the first endcap. double t1a, t2a; if (!sphere_intersects_line(t1a, t2a, 0.0f, from, delta, radius)) { @@ -861,7 +861,7 @@ intersects_line(double &t1, double &t2, t2 = t2a; } else if (t2_y > _length) { - // The ending point is off the top of the tube. Test the line against the + // The ending point is off the top of the capsule. Test the line against the // second endcap. double t1b, t2b; if (!sphere_intersects_line(t1b, t2b, _length, from, delta, radius)) { @@ -880,7 +880,7 @@ intersects_line(double &t1, double &t2, * whether it intersects one or the other endcaps. The y parameter specifies * the center of the sphere (and hence the particular endcap). */ -bool CollisionTube:: +bool CollisionCapsule:: sphere_intersects_line(double &t1, double &t2, PN_stdfloat center_y, const LPoint3 &from, const LVector3 &delta, PN_stdfloat radius) { @@ -917,14 +917,14 @@ sphere_intersects_line(double &t1, double &t2, PN_stdfloat center_y, } /** - * Determine a point of intersection of a parametric parabola with the tube. + * Determine a point of intersection of a parametric parabola with the capsule. * * We only consider the segment of the parabola between t1 and t2, which has * already been computed as corresponding to points p1 and p2. If there is an * intersection, t is set to the parametric point of intersection, and true is * returned; otherwise, false is returned. */ -bool CollisionTube:: +bool CollisionCapsule:: intersects_parabola(double &t, const LParabola ¶bola, double t1, double t2, const LPoint3 &p1, const LPoint3 &p2) const { @@ -965,11 +965,11 @@ intersects_parabola(double &t, const LParabola ¶bola, } /** - * Calculates a point that is exactly on the surface of the tube and its + * Calculates a point that is exactly on the surface of the capsule and its * corresponding normal, given a point that is supposedly on the surface of - * the tube. + * the capsule. */ -void CollisionTube:: +void CollisionCapsule:: calculate_surface_point_and_normal(const LPoint3 &surface_point, double extra_radius, LPoint3 &result_point, @@ -1015,7 +1015,7 @@ calculate_surface_point_and_normal(const LPoint3 &surface_point, * point in the CollisionEntry, and also compute the relevant normal based on * that point. */ -void CollisionTube:: +void CollisionCapsule:: set_intersection_point(CollisionEntry *new_entry, const LPoint3 &into_intersection_point, double extra_radius) const { @@ -1033,16 +1033,16 @@ set_intersection_point(CollisionEntry *new_entry, new_entry->set_surface_normal(normal); new_entry->set_surface_point(point); - // Also adjust the original point into the tube by the amount of - // extra_radius, which should put it on the surface of the tube if our + // Also adjust the original point into the capsule by the amount of + // extra_radius, which should put it on the surface of the capsule if our // collision was tangential. new_entry->set_interior_point(into_intersection_point - normal * extra_radius); } /** - * Tells the BamReader how to create objects of type CollisionTube. + * Tells the BamReader how to create objects of type CollisionCapsule. */ -void CollisionTube:: +void CollisionCapsule:: register_with_read_factory() { BamReader::get_factory()->register_factory(get_class_type(), make_from_bam); } @@ -1051,7 +1051,7 @@ register_with_read_factory() { * Writes the contents of this object to the datagram for shipping out to a * Bam file. */ -void CollisionTube:: +void CollisionCapsule:: write_datagram(BamWriter *manager, Datagram &dg) { CollisionSolid::write_datagram(manager, dg); _a.write_datagram(dg); @@ -1061,12 +1061,12 @@ write_datagram(BamWriter *manager, Datagram &dg) { /** * This function is called by the BamReader's factory when a new object of - * type CollisionTube is encountered in the Bam file. It should create the - * CollisionTube and extract its information from the file. + * type CollisionCapsule is encountered in the Bam file. It should create the + * CollisionCapsule and extract its information from the file. */ -TypedWritable *CollisionTube:: +TypedWritable *CollisionCapsule:: make_from_bam(const FactoryParams ¶ms) { - CollisionTube *node = new CollisionTube(); + CollisionCapsule *node = new CollisionCapsule(); DatagramIterator scan; BamReader *manager; @@ -1078,9 +1078,9 @@ make_from_bam(const FactoryParams ¶ms) { /** * This internal function is called by make_from_bam to read in all of the - * relevant data from the BamFile for the new CollisionTube. + * relevant data from the BamFile for the new CollisionCapsule. */ -void CollisionTube:: +void CollisionCapsule:: fillin(DatagramIterator &scan, BamReader *manager) { CollisionSolid::fillin(scan, manager); _a.read_datagram(scan); diff --git a/panda/src/collide/collisionTube.h b/panda/src/collide/collisionCapsule.h similarity index 81% rename from panda/src/collide/collisionTube.h rename to panda/src/collide/collisionCapsule.h index 3d91ce2506..af5cbcc92d 100644 --- a/panda/src/collide/collisionTube.h +++ b/panda/src/collide/collisionCapsule.h @@ -6,38 +6,39 @@ * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * - * @file collisionTube.h + * @file collisionCapsule.h * @author drose * @date 2003-09-25 */ -#ifndef COLLISIONTUBE_H -#define COLLISIONTUBE_H +#ifndef COLLISIONCAPSULE_H +#define COLLISIONCAPSULE_H #include "pandabase.h" #include "collisionSolid.h" #include "parabola.h" /** - * This implements a solid roughly in cylindrical shape. It's not called a - * CollisionCylinder because it's not a true cylinder; specifically, it has - * rounded ends instead of flat ends. It looks more like a Contac pill. + * This implements a solid consisting of a cylinder with hemispherical endcaps, + * also known as a capsule or a spherocylinder. + * + * This shape was previously erroneously called CollisionTube. */ -class EXPCL_PANDA_COLLIDE CollisionTube : public CollisionSolid { +class EXPCL_PANDA_COLLIDE CollisionCapsule : public CollisionSolid { PUBLISHED: - INLINE explicit CollisionTube(const LPoint3 &a, const LPoint3 &db, - PN_stdfloat radius); - INLINE explicit CollisionTube(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az, - PN_stdfloat bx, PN_stdfloat by, PN_stdfloat bz, - PN_stdfloat radius); + INLINE explicit CollisionCapsule(const LPoint3 &a, const LPoint3 &db, + PN_stdfloat radius); + INLINE explicit CollisionCapsule(PN_stdfloat ax, PN_stdfloat ay, PN_stdfloat az, + PN_stdfloat bx, PN_stdfloat by, PN_stdfloat bz, + PN_stdfloat radius); virtual LPoint3 get_collision_origin() const; private: - INLINE CollisionTube(); + INLINE CollisionCapsule(); public: - INLINE CollisionTube(const CollisionTube ©); + INLINE CollisionCapsule(const CollisionCapsule ©); virtual CollisionSolid *make_copy(); virtual PT(CollisionEntry) @@ -82,7 +83,7 @@ protected: virtual PT(CollisionEntry) test_intersection_from_segment(const CollisionEntry &entry) const; virtual PT(CollisionEntry) - test_intersection_from_tube(const CollisionEntry &entry) const; + test_intersection_from_capsule(const CollisionEntry &entry) const; virtual PT(CollisionEntry) test_intersection_from_parabola(const CollisionEntry &entry) const; @@ -141,7 +142,7 @@ public: } static void init_type() { CollisionSolid::init_type(); - register_type(_type_handle, "CollisionTube", + register_type(_type_handle, "CollisionCapsule", CollisionSolid::get_class_type()); } virtual TypeHandle get_type() const { @@ -155,6 +156,13 @@ private: friend class CollisionBox; }; -#include "collisionTube.I" +BEGIN_PUBLISH +/** + * Alias for backward compatibility. + */ +typedef CollisionCapsule CollisionTube; +END_PUBLISH + +#include "collisionCapsule.I" #endif diff --git a/panda/src/collide/collisionHandlerFluidPusher.cxx b/panda/src/collide/collisionHandlerFluidPusher.cxx index df15e8bacc..622f5da00d 100644 --- a/panda/src/collide/collisionHandlerFluidPusher.cxx +++ b/panda/src/collide/collisionHandlerFluidPusher.cxx @@ -72,7 +72,7 @@ handle_entries() { PosB = collider's current position M = movement vector (PosB - PosA) BV = bounding sphere that includes collider at PosA and PosB - CS = 'collision set', all 'collidables' within BV (collision polys, tubes, etc) + CS = 'collision set', all 'collidables' within BV (collision polys, capsules, etc) VARIABLES N = movement vector since most recent collision (or start of frame) diff --git a/panda/src/collide/collisionPlane.cxx b/panda/src/collide/collisionPlane.cxx index 8a325ea082..2e174d4aa8 100644 --- a/panda/src/collide/collisionPlane.cxx +++ b/panda/src/collide/collisionPlane.cxx @@ -18,7 +18,7 @@ #include "collisionLine.h" #include "collisionRay.h" #include "collisionSegment.h" -#include "collisionTube.h" +#include "collisionCapsule.h" #include "collisionParabola.h" #include "config_collide.h" #include "pointerToArray.h" @@ -298,16 +298,16 @@ test_intersection_from_segment(const CollisionEntry &entry) const { * */ PT(CollisionEntry) CollisionPlane:: -test_intersection_from_tube(const CollisionEntry &entry) const { - const CollisionTube *tube; - DCAST_INTO_R(tube, entry.get_from(), nullptr); +test_intersection_from_capsule(const CollisionEntry &entry) const { + const CollisionCapsule *capsule; + DCAST_INTO_R(capsule, entry.get_from(), nullptr); const LMatrix4 &wrt_mat = entry.get_wrt_mat(); - LPoint3 from_a = tube->get_point_a() * wrt_mat; - LPoint3 from_b = tube->get_point_b() * wrt_mat; + LPoint3 from_a = capsule->get_point_a() * wrt_mat; + LPoint3 from_b = capsule->get_point_b() * wrt_mat; LVector3 from_radius_v = - LVector3(tube->get_radius(), 0.0f, 0.0f) * wrt_mat; + LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat; PN_stdfloat from_radius = length(from_radius_v); PN_stdfloat dist_a = _plane.dist_to_plane(from_a); @@ -325,7 +325,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const { } PT(CollisionEntry) new_entry = new CollisionEntry(entry); - LVector3 normal = (has_effective_normal() && tube->get_respect_effective_normal()) ? get_effective_normal() : get_normal(); + LVector3 normal = (has_effective_normal() && capsule->get_respect_effective_normal()) ? get_effective_normal() : get_normal(); new_entry->set_surface_normal(normal); PN_stdfloat t; @@ -339,17 +339,17 @@ test_intersection_from_tube(const CollisionEntry &entry) const { new_entry->set_surface_point(from_a - get_normal() * dist_a); } else { - // Within the tube! Yay, that means we have a surface point. + // Within the capsule! Yay, that means we have a surface point. new_entry->set_surface_point(from_a + t * from_direction); } } else { // If it's completely parallel, pretend it's colliding in the center of - // the tube. + // the capsule. new_entry->set_surface_point(from_a + 0.5f * from_direction - get_normal() * dist_a); } if (IS_NEARLY_EQUAL(dist_a, dist_b)) { - // Let's be fair and choose the center of the tube. + // Let's be fair and choose the center of the capsule. new_entry->set_interior_point(from_a + 0.5f * from_direction - get_normal() * from_radius); } else if (dist_a < dist_b) { diff --git a/panda/src/collide/collisionPlane.h b/panda/src/collide/collisionPlane.h index bc7602ba8b..719e33f489 100644 --- a/panda/src/collide/collisionPlane.h +++ b/panda/src/collide/collisionPlane.h @@ -72,7 +72,7 @@ protected: virtual PT(CollisionEntry) test_intersection_from_segment(const CollisionEntry &entry) const; virtual PT(CollisionEntry) - test_intersection_from_tube(const CollisionEntry &entry) const; + test_intersection_from_capsule(const CollisionEntry &entry) const; virtual PT(CollisionEntry) test_intersection_from_parabola(const CollisionEntry &entry) const; virtual PT(CollisionEntry) diff --git a/panda/src/collide/collisionSolid.cxx b/panda/src/collide/collisionSolid.cxx index b862ec7c73..b68673be15 100644 --- a/panda/src/collide/collisionSolid.cxx +++ b/panda/src/collide/collisionSolid.cxx @@ -17,7 +17,7 @@ #include "collisionLine.h" #include "collisionRay.h" #include "collisionSegment.h" -#include "collisionTube.h" +#include "collisionCapsule.h" #include "collisionParabola.h" #include "collisionBox.h" #include "collisionEntry.h" @@ -240,11 +240,11 @@ test_intersection_from_segment(const CollisionEntry &) const { /** * This is part of the double-dispatch implementation of test_intersection(). - * It is called when the "from" object is a tube. + * It is called when the "from" object is a capsule. */ PT(CollisionEntry) CollisionSolid:: -test_intersection_from_tube(const CollisionEntry &) const { - report_undefined_intersection_test(CollisionTube::get_class_type(), +test_intersection_from_capsule(const CollisionEntry &) const { + report_undefined_intersection_test(CollisionCapsule::get_class_type(), get_type()); return nullptr; } diff --git a/panda/src/collide/collisionSolid.h b/panda/src/collide/collisionSolid.h index 6b2914e954..4721d70381 100644 --- a/panda/src/collide/collisionSolid.h +++ b/panda/src/collide/collisionSolid.h @@ -108,7 +108,7 @@ protected: virtual PT(CollisionEntry) test_intersection_from_segment(const CollisionEntry &entry) const; virtual PT(CollisionEntry) - test_intersection_from_tube(const CollisionEntry &entry) const; + test_intersection_from_capsule(const CollisionEntry &entry) const; virtual PT(CollisionEntry) test_intersection_from_parabola(const CollisionEntry &entry) const; virtual PT(CollisionEntry) @@ -177,7 +177,7 @@ private: friend class CollisionLine; friend class CollisionRay; friend class CollisionSegment; - friend class CollisionTube; + friend class CollisionCapsule; friend class CollisionParabola; friend class CollisionHandlerFluidPusher; friend class CollisionBox; diff --git a/panda/src/collide/collisionSphere.cxx b/panda/src/collide/collisionSphere.cxx index a07b1fbcbc..aacc43fcb2 100644 --- a/panda/src/collide/collisionSphere.cxx +++ b/panda/src/collide/collisionSphere.cxx @@ -17,7 +17,7 @@ #include "collisionHandler.h" #include "collisionEntry.h" #include "collisionSegment.h" -#include "collisionTube.h" +#include "collisionCapsule.h" #include "collisionParabola.h" #include "collisionBox.h" #include "config_collide.h" @@ -444,18 +444,18 @@ test_intersection_from_segment(const CollisionEntry &entry) const { * */ PT(CollisionEntry) CollisionSphere:: -test_intersection_from_tube(const CollisionEntry &entry) const { - const CollisionTube *tube; - DCAST_INTO_R(tube, entry.get_from(), nullptr); +test_intersection_from_capsule(const CollisionEntry &entry) const { + const CollisionCapsule *capsule; + DCAST_INTO_R(capsule, entry.get_from(), nullptr); const LMatrix4 &wrt_mat = entry.get_wrt_mat(); - LPoint3 from_a = tube->get_point_a() * wrt_mat; - LPoint3 from_b = tube->get_point_b() * wrt_mat; + LPoint3 from_a = capsule->get_point_a() * wrt_mat; + LPoint3 from_b = capsule->get_point_b() * wrt_mat; LVector3 from_direction = from_b - from_a; LVector3 from_radius_v = - LVector3(tube->get_radius(), 0.0f, 0.0f) * wrt_mat; + LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat; PN_stdfloat from_radius = length(from_radius_v); double t1, t2; @@ -465,8 +465,8 @@ test_intersection_from_tube(const CollisionEntry &entry) const { } if (t2 < 0.0 || t1 > 1.0) { - // Both intersection points are before the start of the tube or after - // the end of the tube. + // Both intersection points are before the start of the capsule or after + // the end of the capsule. return nullptr; } @@ -487,7 +487,7 @@ test_intersection_from_tube(const CollisionEntry &entry) const { new_entry->set_surface_point(get_center() + normal * get_radius()); new_entry->set_interior_point(inner_point - normal * from_radius); - if (has_effective_normal() && tube->get_respect_effective_normal()) { + if (has_effective_normal() && capsule->get_respect_effective_normal()) { new_entry->set_surface_normal(get_effective_normal()); } else { new_entry->set_surface_normal(normal); diff --git a/panda/src/collide/collisionSphere.h b/panda/src/collide/collisionSphere.h index 8337644b42..39b21d59f1 100644 --- a/panda/src/collide/collisionSphere.h +++ b/panda/src/collide/collisionSphere.h @@ -72,7 +72,7 @@ protected: virtual PT(CollisionEntry) test_intersection_from_segment(const CollisionEntry &entry) const; virtual PT(CollisionEntry) - test_intersection_from_tube(const CollisionEntry &entry) const; + test_intersection_from_capsule(const CollisionEntry &entry) const; virtual PT(CollisionEntry) test_intersection_from_parabola(const CollisionEntry &entry) const; virtual PT(CollisionEntry) diff --git a/panda/src/collide/collisionTraverser.cxx b/panda/src/collide/collisionTraverser.cxx index 35f7966a3f..794e7552da 100644 --- a/panda/src/collide/collisionTraverser.cxx +++ b/panda/src/collide/collisionTraverser.cxx @@ -20,7 +20,7 @@ #include "collisionVisualizer.h" #include "collisionSphere.h" #include "collisionBox.h" -#include "collisionTube.h" +#include "collisionCapsule.h" #include "collisionPolygon.h" #include "collisionPlane.h" #include "config_collide.h" @@ -345,7 +345,7 @@ traverse(const NodePath &root) { _geom_volume_pcollector.flush_level(); CollisionSphere::flush_level(); - CollisionTube::flush_level(); + CollisionCapsule::flush_level(); CollisionPolygon::flush_level(); CollisionPlane::flush_level(); CollisionBox::flush_level(); diff --git a/panda/src/collide/config_collide.cxx b/panda/src/collide/config_collide.cxx index 38c821ed05..b9ca4e368a 100644 --- a/panda/src/collide/config_collide.cxx +++ b/panda/src/collide/config_collide.cxx @@ -13,6 +13,7 @@ #include "config_collide.h" #include "collisionBox.h" +#include "collisionCapsule.h" #include "collisionEntry.h" #include "collisionHandler.h" #include "collisionHandlerEvent.h" @@ -38,7 +39,6 @@ #include "collisionSolid.h" #include "collisionSphere.h" #include "collisionTraverser.h" -#include "collisionTube.h" #include "collisionVisualizer.h" #include "dconfig.h" @@ -126,6 +126,7 @@ init_libcollide() { initialized = true; CollisionBox::init_type(); + CollisionCapsule::init_type(); CollisionEntry::init_type(); CollisionHandler::init_type(); CollisionHandlerEvent::init_type(); @@ -150,14 +151,18 @@ init_libcollide() { CollisionSolid::init_type(); CollisionSphere::init_type(); CollisionTraverser::init_type(); - CollisionTube::init_type(); #ifdef DO_COLLISION_RECORDING CollisionRecorder::init_type(); CollisionVisualizer::init_type(); #endif + // Record the old name for CollisionCapsule for backwards compatibility. + BamWriter::record_obsolete_type_name(CollisionCapsule::get_class_type(), + "CollisionTube", 6, 44); + CollisionBox::register_with_read_factory(); + CollisionCapsule::register_with_read_factory(); CollisionInvSphere::register_with_read_factory(); CollisionLine::register_with_read_factory(); CollisionNode::register_with_read_factory(); @@ -168,5 +173,4 @@ init_libcollide() { CollisionRay::register_with_read_factory(); CollisionSegment::register_with_read_factory(); CollisionSphere::register_with_read_factory(); - CollisionTube::register_with_read_factory(); } diff --git a/panda/src/collide/p3collide_composite1.cxx b/panda/src/collide/p3collide_composite1.cxx index d66b71b18c..af8ecc07d8 100644 --- a/panda/src/collide/p3collide_composite1.cxx +++ b/panda/src/collide/p3collide_composite1.cxx @@ -1,5 +1,6 @@ #include "config_collide.cxx" #include "collisionBox.cxx" +#include "collisionCapsule.cxx" #include "collisionEntry.cxx" #include "collisionGeom.cxx" #include "collisionHandler.cxx" @@ -12,5 +13,3 @@ #include "collisionHandlerFluidPusher.cxx" #include "collisionHandlerQueue.cxx" #include "collisionInvSphere.cxx" -#include "collisionLevelStateBase.cxx" -#include "collisionLevelState.cxx" diff --git a/panda/src/collide/p3collide_composite2.cxx b/panda/src/collide/p3collide_composite2.cxx index 50a6acdddc..4b74e9dd9e 100644 --- a/panda/src/collide/p3collide_composite2.cxx +++ b/panda/src/collide/p3collide_composite2.cxx @@ -1,3 +1,5 @@ +#include "collisionLevelStateBase.cxx" +#include "collisionLevelState.cxx" #include "collisionLine.cxx" #include "collisionNode.cxx" #include "collisionParabola.cxx" @@ -10,5 +12,4 @@ #include "collisionSolid.cxx" #include "collisionSphere.cxx" #include "collisionTraverser.cxx" -#include "collisionTube.cxx" #include "collisionVisualizer.cxx" diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index c06c2b207f..ae1052ba1d 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/panda/src/display/graphicsEngine.cxx @@ -110,8 +110,8 @@ PStatCollector GraphicsEngine::_volume_sphere_pcollector("Collision Volumes:Coll PStatCollector GraphicsEngine::_test_sphere_pcollector("Collision Tests:CollisionSphere"); PStatCollector GraphicsEngine::_volume_box_pcollector("Collision Volumes:CollisionBox"); PStatCollector GraphicsEngine::_test_box_pcollector("Collision Tests:CollisionBox"); -PStatCollector GraphicsEngine::_volume_tube_pcollector("Collision Volumes:CollisionTube"); -PStatCollector GraphicsEngine::_test_tube_pcollector("Collision Tests:CollisionTube"); +PStatCollector GraphicsEngine::_volume_capsule_pcollector("Collision Volumes:CollisionCapsule"); +PStatCollector GraphicsEngine::_test_capsule_pcollector("Collision Tests:CollisionCapsule"); PStatCollector GraphicsEngine::_volume_inv_sphere_pcollector("Collision Volumes:CollisionInvSphere"); PStatCollector GraphicsEngine::_test_inv_sphere_pcollector("Collision Tests:CollisionInvSphere"); PStatCollector GraphicsEngine::_volume_geom_pcollector("Collision Volumes:CollisionGeom"); @@ -875,8 +875,8 @@ render_frame() { _test_sphere_pcollector.clear_level(); _volume_box_pcollector.clear_level(); _test_box_pcollector.clear_level(); - _volume_tube_pcollector.clear_level(); - _test_tube_pcollector.clear_level(); + _volume_capsule_pcollector.clear_level(); + _test_capsule_pcollector.clear_level(); _volume_inv_sphere_pcollector.clear_level(); _test_inv_sphere_pcollector.clear_level(); _volume_geom_pcollector.clear_level(); diff --git a/panda/src/display/graphicsEngine.h b/panda/src/display/graphicsEngine.h index 3371f20b39..e3e7df66c8 100644 --- a/panda/src/display/graphicsEngine.h +++ b/panda/src/display/graphicsEngine.h @@ -401,8 +401,8 @@ private: static PStatCollector _test_sphere_pcollector; static PStatCollector _volume_box_pcollector; static PStatCollector _test_box_pcollector; - static PStatCollector _volume_tube_pcollector; - static PStatCollector _test_tube_pcollector; + static PStatCollector _volume_capsule_pcollector; + static PStatCollector _test_capsule_pcollector; static PStatCollector _volume_inv_sphere_pcollector; static PStatCollector _test_inv_sphere_pcollector; static PStatCollector _volume_geom_pcollector; diff --git a/panda/src/doc/collisionFlags.txt b/panda/src/doc/collisionFlags.txt index 4989ef8e33..04a890f42b 100644 --- a/panda/src/doc/collisionFlags.txt +++ b/panda/src/doc/collisionFlags.txt @@ -6,7 +6,7 @@ camera-collide: for things that the camera should avoid trigger: for things (usually not barriers or floors) that should trigger an event when avatars intersect with them sphere: for things that should have a collision sphere around them -tube: for things that should have a collision tube (cylinder) around them +tube: for things that should have a collision capsule around them NOTES diff --git a/panda/src/egg/eggGroup.cxx b/panda/src/egg/eggGroup.cxx index 6bc6f152f0..fe36f2b123 100644 --- a/panda/src/egg/eggGroup.cxx +++ b/panda/src/egg/eggGroup.cxx @@ -897,7 +897,8 @@ string_cs_type(const string &strval) { } else if (cmp_nocase_uh(strval, "inv-sphere") == 0 || cmp_nocase_uh(strval, "invsphere") == 0) { return CST_inv_sphere; - } else if (cmp_nocase_uh(strval, "tube") == 0) { + } else if (cmp_nocase_uh(strval, "tube") == 0 || + cmp_nocase_uh(strval, "capsule") == 0) { return CST_tube; } else if (cmp_nocase_uh(strval, "floor-mesh") == 0 || cmp_nocase_uh(strval, "floormesh") == 0) { diff --git a/panda/src/egg2pg/eggLoader.cxx b/panda/src/egg2pg/eggLoader.cxx index 9d885e0234..6aeb84f54f 100644 --- a/panda/src/egg2pg/eggLoader.cxx +++ b/panda/src/egg2pg/eggLoader.cxx @@ -74,7 +74,7 @@ #include "collisionNode.h" #include "collisionSphere.h" #include "collisionInvSphere.h" -#include "collisionTube.h" +#include "collisionCapsule.h" #include "collisionPlane.h" #include "collisionPolygon.h" #include "collisionFloorMesh.h" @@ -2865,7 +2865,7 @@ make_collision_solids(EggGroup *start_group, EggGroup *egg_group, break; case EggGroup::CST_tube: - make_collision_tube(egg_group, cnode, start_group->get_collide_flags()); + make_collision_capsule(egg_group, cnode, start_group->get_collide_flags()); break; case EggGroup::CST_floor_mesh: @@ -3045,12 +3045,12 @@ make_collision_inv_sphere(EggGroup *egg_group, CollisionNode *cnode, } /** - * Creates a single CollisionTube corresponding to the polygons associated + * Creates a single CollisionCapsule corresponding to the polygons associated * with this group. */ void EggLoader:: -make_collision_tube(EggGroup *egg_group, CollisionNode *cnode, - EggGroup::CollideFlags flags) { +make_collision_capsule(EggGroup *egg_group, CollisionNode *cnode, + EggGroup::CollideFlags flags) { EggGroup *geom_group = find_collision_geometry(egg_group, flags); if (geom_group != nullptr) { // Collect all of the vertices. @@ -3175,7 +3175,7 @@ make_collision_tube(EggGroup *egg_group, CollisionNode *cnode, // Transform all of the points so that the major axis is along the Y // axis, and the origin is the center. This is very similar to the - // CollisionTube's idea of its canonical orientation (although not + // CollisionCapsule's idea of its canonical orientation (although not // exactly the same, since it is centered on the origin instead of // having point_a on the origin). It makes it easier to determine the // length and radius of the cylinder. @@ -3230,11 +3230,11 @@ make_collision_tube(EggGroup *egg_group, CollisionNode *cnode, LPoint3d point_a = center - half; LPoint3d point_b = center + half; - CollisionTube *cstube = - new CollisionTube(LCAST(PN_stdfloat, point_a), LCAST(PN_stdfloat, point_b), + CollisionCapsule *cscapsule = + new CollisionCapsule(LCAST(PN_stdfloat, point_a), LCAST(PN_stdfloat, point_b), radius); - apply_collision_flags(cstube, flags); - cnode->add_solid(cstube); + apply_collision_flags(cscapsule, flags); + cnode->add_solid(cscapsule); } } } diff --git a/panda/src/egg2pg/eggLoader.h b/panda/src/egg2pg/eggLoader.h index eb81317bd8..0f974f41c2 100644 --- a/panda/src/egg2pg/eggLoader.h +++ b/panda/src/egg2pg/eggLoader.h @@ -180,8 +180,8 @@ private: EggGroup::CollideFlags flags); void make_collision_inv_sphere(EggGroup *egg_group, CollisionNode *cnode, EggGroup::CollideFlags flags); - void make_collision_tube(EggGroup *egg_group, CollisionNode *cnode, - EggGroup::CollideFlags flags); + void make_collision_capsule(EggGroup *egg_group, CollisionNode *cnode, + EggGroup::CollideFlags flags); void make_collision_floor_mesh(EggGroup *egg_group, CollisionNode *cnode, EggGroup::CollideFlags flags); void apply_collision_flags(CollisionSolid *solid, diff --git a/panda/src/egg2pg/eggSaver.cxx b/panda/src/egg2pg/eggSaver.cxx index fb6bb3a59b..9aba56cc01 100644 --- a/panda/src/egg2pg/eggSaver.cxx +++ b/panda/src/egg2pg/eggSaver.cxx @@ -39,7 +39,7 @@ #include "collisionSphere.h" #include "collisionBox.h" #include "collisionInvSphere.h" -#include "collisionTube.h" +#include "collisionCapsule.h" #include "textureStage.h" #include "geomNode.h" #include "geom.h" @@ -620,13 +620,13 @@ convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path, egg_poly->add_vertex(cvpool->create_unique_vertex(ev0)); egg_poly->add_vertex(cvpool->create_unique_vertex(ev1)); - } else if (child->is_of_type(CollisionTube::get_class_type())) { - CPT(CollisionTube) tube = DCAST(CollisionTube, child); - LPoint3 point_a = tube->get_point_a(); - LPoint3 point_b = tube->get_point_b(); + } else if (child->is_of_type(CollisionCapsule::get_class_type())) { + CPT(CollisionCapsule) capsule = DCAST(CollisionCapsule, child); + LPoint3 point_a = capsule->get_point_a(); + LPoint3 point_b = capsule->get_point_b(); LPoint3 centroid = (point_a + point_b) * 0.5f; - // Also get an arbitrary vector perpendicular to the tube. + // Also get an arbitrary vector perpendicular to the capsule. LVector3 axis = point_b - point_a; LVector3 sideways; if (std::fabs(axis[2]) > std::fabs(axis[1])) { @@ -635,18 +635,18 @@ convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path, sideways = axis.cross(LVector3(0, 0, 1)); } sideways.normalize(); - sideways *= tube->get_radius(); - LVector3 extend = axis.normalized() * tube->get_radius(); + sideways *= capsule->get_radius(); + LVector3 extend = axis.normalized() * capsule->get_radius(); - EggGroup *egg_tube; + EggGroup *egg_capsule; if (num_solids == 1) { - egg_tube = egg_group; + egg_capsule = egg_group; } else { - egg_tube = new EggGroup; - egg_group->add_child(egg_tube); + egg_capsule = new EggGroup; + egg_group->add_child(egg_capsule); } - egg_tube->set_cs_type(EggGroup::CST_tube); - egg_tube->set_collide_flags(flags); + egg_capsule->set_cs_type(EggGroup::CST_tube); + egg_capsule->set_collide_flags(flags); // Add two points for the endcaps, and then two points around the // centroid to indicate the radius. @@ -657,7 +657,7 @@ convert_collision_node(CollisionNode *node, const WorkingNodePath &node_path, ev3.set_pos(LCAST(double, (centroid - sideways) * net_mat)); EggPolygon *egg_poly = new EggPolygon; - egg_tube->add_child(egg_poly); + egg_capsule->add_child(egg_poly); egg_poly->add_vertex(cvpool->create_unique_vertex(ev0)); egg_poly->add_vertex(cvpool->create_unique_vertex(ev1)); diff --git a/panda/src/putil/bam.h b/panda/src/putil/bam.h index 96f41cbf98..e21dadbc14 100644 --- a/panda/src/putil/bam.h +++ b/panda/src/putil/bam.h @@ -32,7 +32,7 @@ static const unsigned short _bam_major_ver = 6; // Bumped to major version 6 on 2006-02-11 to factor out PandaNode::CData. static const unsigned short _bam_first_minor_ver = 14; -static const unsigned short _bam_minor_ver = 43; +static const unsigned short _bam_minor_ver = 44; // Bumped to minor version 14 on 2007-12-19 to change default ColorAttrib. // Bumped to minor version 15 on 2008-04-09 to add TextureAttrib::_implicit_sort. // Bumped to minor version 16 on 2008-05-13 to add Texture::_quality_level. @@ -63,5 +63,6 @@ static const unsigned short _bam_minor_ver = 43; // Bumped to minor version 41 on 2016-03-02 to change LensNode, Lens, and Camera. // Bumped to minor version 42 on 2016-04-08 to expand ColorBlendAttrib. // Bumped to minor version 43 on 2018-12-06 to expand BillboardEffect and CompassEffect. +// Bumped to minor version 44 on 2018-12-23 to rename CollisionTube to CollisionCapsule. #endif diff --git a/panda/src/putil/bamWriter.cxx b/panda/src/putil/bamWriter.cxx index d8aa3e0b30..3de731d27d 100644 --- a/panda/src/putil/bamWriter.cxx +++ b/panda/src/putil/bamWriter.cxx @@ -20,9 +20,28 @@ #include "bamWriter.h" #include "bamReader.h" #include "lightMutexHolder.h" +#include "simpleHashMap.h" #include +// Keeps track of older type names in case we want to write out older .bam +// files. +struct ObsoleteName { + std::string _name; + int _before_major; + int _before_minor; + + bool operator < (const ObsoleteName &other) const { + if (_before_major != other._before_major) { + return _before_major < other._before_major; + } + return _before_minor < other._before_minor; + } +}; + +// This is a SimpleHashMap to avoid static init ordering issues. +static SimpleHashMap > obsolete_type_names; + /** * */ @@ -504,7 +523,14 @@ write_handle(Datagram &packet, TypeHandle type) { if (inserted) { // This is the first time this TypeHandle has been written, so also // write out its definition. - packet.add_string(type.get_name()); + + if (_file_major == _bam_major_ver && _file_minor == _bam_minor_ver) { + packet.add_string(type.get_name()); + } else { + // We are writing an older .bam format, so we need to look up whether + // we may need to write an older type name. + packet.add_string(get_obsolete_type_name(type, _file_major, _file_minor)); + } // We also need to write the derivation of the TypeHandle, in case the // program reading this file later has never heard of this type before. @@ -518,6 +544,45 @@ write_handle(Datagram &packet, TypeHandle type) { } } +/** + * Returns the name that the given type had in an older .bam version. + */ +std::string BamWriter:: +get_obsolete_type_name(TypeHandle type, int major, int minor) { + int index = obsolete_type_names.find(type); + if (index >= 0) { + // Iterate over the names. It is sorted such that the lower versions are + // listed first. + for (const ObsoleteName &name : obsolete_type_names.get_data((size_t)index)) { + if (major < name._before_major || + (major == name._before_major && minor < name._before_minor)) { + // We have a hit. + return name._name; + } + } + } + + return TypeRegistry::ptr()->get_name(type, nullptr); +} + +/** + * Registers the given type as having an older name in .bam files *before* the + * indicated version. You can call this multiple times for the same type in + * order to establish a history of renames for this type. + */ +void BamWriter:: +record_obsolete_type_name(TypeHandle type, std::string name, + int before_major, int before_minor) { + // Make sure it is registered as alternate name for reading. + TypeRegistry *reg = TypeRegistry::ptr(); + reg->record_alternate_name(type, name); + + ObsoleteName obsolete_name; + obsolete_name._name = std::move(name); + obsolete_name._before_major = before_major; + obsolete_name._before_minor = before_minor; + obsolete_type_names[type].insert(std::move(obsolete_name)); +} /** * This is called by the TypedWritable destructor. It should remove the diff --git a/panda/src/putil/bamWriter.h b/panda/src/putil/bamWriter.h index b8c53aac38..780104b61c 100644 --- a/panda/src/putil/bamWriter.h +++ b/panda/src/putil/bamWriter.h @@ -111,6 +111,10 @@ public: bool register_pta(Datagram &packet, const void *ptr); void write_handle(Datagram &packet, TypeHandle type); + static std::string get_obsolete_type_name(TypeHandle type, int major, int minor); + static void record_obsolete_type_name(TypeHandle type, std::string name, + int before_major, int before_minor); + private: void object_destructs(TypedWritable *object); diff --git a/pandatool/src/eggprogs/eggMakeTube.cxx b/pandatool/src/eggprogs/eggMakeTube.cxx index 7017e97dcd..85ec85081d 100644 --- a/pandatool/src/eggprogs/eggMakeTube.cxx +++ b/pandatool/src/eggprogs/eggMakeTube.cxx @@ -29,7 +29,7 @@ EggMakeTube() { set_program_description ("egg-make-tube generates an egg file representing a \"tube\" model, " "a cylinder capped on both ends by hemispheres. This is similar " - "in shape to the CollisionTube object within Panda.\n\n" + "in shape to the CollisionCapsule object within Panda.\n\n" "This program can also generate spheres if you omit -b; in this " "case, you are generating a degenerate tube of length 0."); diff --git a/pandatool/src/eggprogs/eggMakeTube.h b/pandatool/src/eggprogs/eggMakeTube.h index 520d178aee..3031b1c74e 100644 --- a/pandatool/src/eggprogs/eggMakeTube.h +++ b/pandatool/src/eggprogs/eggMakeTube.h @@ -24,7 +24,7 @@ class EggVertex; /** * A program to generate an egg file representing a tube model, similar in - * shape to a CollisionTube. + * shape to a CollisionCapsule. */ class EggMakeTube : public EggMakeSomething { public: diff --git a/tests/collide/test_collision_tube.py b/tests/collide/test_collision_tube.py new file mode 100644 index 0000000000..3d97754d87 --- /dev/null +++ b/tests/collide/test_collision_tube.py @@ -0,0 +1,63 @@ +from panda3d import core + + +def test_collision_tube_alias(): + assert hasattr(core, 'CollisionCapsule') + assert hasattr(core, 'CollisionTube') + assert core.CollisionTube is core.CollisionCapsule + + +def test_collision_tube_write_old(): + buffer = core.DatagramBuffer() + writer = core.BamWriter(buffer) + assert writer.get_file_major_ver() == 6 + writer.set_file_minor_ver(43) + + capsule = core.CollisionCapsule((0, 0, -1), (0, 0, 1), 0.5) + writer.init() + writer.write_object(capsule) + writer.flush() + + data = buffer.data + assert b'CollisionTube' in data + assert b'CollisionCapsule' not in data + + +def test_collision_tube_write_new(): + buffer = core.DatagramBuffer() + writer = core.BamWriter(buffer) + assert writer.get_file_major_ver() == 6 + writer.set_file_minor_ver(44) + + capsule = core.CollisionCapsule((0, 0, -1), (0, 0, 1), 0.5) + writer.init() + writer.write_object(capsule) + writer.flush() + + data = buffer.data + assert b'CollisionTube' not in data + assert b'CollisionCapsule' in data + + +def test_collision_tube_read_old(): + # Make sure we can read an older file that contains CollisionTube. + buffer = core.DatagramBuffer(b'\x06\x00\x00\x00\x06\x00+\x00\x01\x00\xd6\x00\x00\x00\x00j\x01\r\x00CollisionTube\x01h\x01\x0e\x00CollisionSolid\x01B\x00\x11\x00CopyOnWriteObject\x01A\x00!\x00CachedTypedWritableReferenceCount\x01=\x00\x1b\x00TypedWritableReferenceCount\x02<\x00\r\x00TypedWritable\x01\x03\x00\x0b\x00TypedObject\x00\x07\x00\x0e\x00ReferenceCount\x00\x01\x00\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00?\x01\x00\x00\x00\x01') + reader = core.BamReader(buffer) + reader.init() + assert reader.file_version == (6, 43) + + capsule = reader.read_object() + reader.resolve() + assert isinstance(capsule, core.CollisionCapsule) + + +def test_collision_tube_read_new(): + # Make sure we can read a newer file that contains CollisionCapsule. + buffer = core.DatagramBuffer(b'\x06\x00\x00\x00\x06\x00,\x00\x01\x00\xd9\x00\x00\x00\x00j\x01\x10\x00CollisionCapsule\x01h\x01\x0e\x00CollisionSolid\x01B\x00\x11\x00CopyOnWriteObject\x01A\x00!\x00CachedTypedWritableReferenceCount\x01=\x00\x1b\x00TypedWritableReferenceCount\x02<\x00\r\x00TypedWritable\x01\x03\x00\x0b\x00TypedObject\x00\x07\x00\x0e\x00ReferenceCount\x00\x01\x00\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80?\x00\x00\x00?\x01\x00\x00\x00\x01') + reader = core.BamReader(buffer) + reader.init() + assert reader.file_version == (6, 44) + + capsule = reader.read_object() + reader.resolve() + assert isinstance(capsule, core.CollisionCapsule) From e2574af82dde2ecb0c8d63a2db64e87d0e47e240 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 19:49:00 +0100 Subject: [PATCH 367/424] collide: create compatibility header collisionTube.h This is a separate commit from the previous one in order not to confuse Git's rename tracking. --- panda/src/collide/collisionCapsule.h | 7 ------- panda/src/collide/collisionTube.h | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 panda/src/collide/collisionTube.h diff --git a/panda/src/collide/collisionCapsule.h b/panda/src/collide/collisionCapsule.h index af5cbcc92d..b6d375ff44 100644 --- a/panda/src/collide/collisionCapsule.h +++ b/panda/src/collide/collisionCapsule.h @@ -156,13 +156,6 @@ private: friend class CollisionBox; }; -BEGIN_PUBLISH -/** - * Alias for backward compatibility. - */ -typedef CollisionCapsule CollisionTube; -END_PUBLISH - #include "collisionCapsule.I" #endif diff --git a/panda/src/collide/collisionTube.h b/panda/src/collide/collisionTube.h new file mode 100644 index 0000000000..c67708628e --- /dev/null +++ b/panda/src/collide/collisionTube.h @@ -0,0 +1,27 @@ +/** + * 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 collisionTube.h + * @author rdb + * @date 2018-12-23 + */ + +#ifndef COLLISIONTUBE_H +#define COLLISIONTUBE_H + +#include "collisionCapsule.h" + +BEGIN_PUBLISH +/** + * Alias for backward compatibility. + * @deprecated use CollisionCapsule instead. + */ +typedef CollisionCapsule CollisionTube; +END_PUBLISH + +#endif From 8cb7f7d9c0ba38cc6f1d3839aab19cd74c71c8c3 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 20:07:05 +0100 Subject: [PATCH 368/424] ffmpeg: don't hold AV lock while fetching first packet/frame This allows loading multiple videos side-by-side more effectively. Closes #397 --- panda/src/ffmpeg/ffmpegVideoCursor.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panda/src/ffmpeg/ffmpegVideoCursor.cxx b/panda/src/ffmpeg/ffmpegVideoCursor.cxx index ef23a9414e..fa5789b6c4 100644 --- a/panda/src/ffmpeg/ffmpegVideoCursor.cxx +++ b/panda/src/ffmpeg/ffmpegVideoCursor.cxx @@ -79,8 +79,6 @@ init_from(FfmpegVideo *source) { return; } - ReMutexHolder av_holder(_av_lock); - #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 45, 101) _frame = av_frame_alloc(); _frame_out = av_frame_alloc(); @@ -109,6 +107,8 @@ init_from(FfmpegVideo *source) { _eof_known = false; _eof_frame = 0; + ReMutexHolder av_holder(_av_lock); + // Check if we got an alpha format. Please note that some video codecs // (eg. libvpx) change the pix_fmt after decoding the first frame, which is // why we didn't do this earlier. From 395b9733fb05940756390aba6f50577f4e1591a6 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 19:52:49 +0100 Subject: [PATCH 369/424] makepanda: when making Linux package, ship Python 2 and 3 bindings See #441 for discussion. --- makepanda/installpanda.py | 44 ++++++++--------- makepanda/makepackage.py | 96 +++++++++++++++++++++++++++----------- makepanda/makepanda.py | 12 ++++- makepanda/makepandacore.py | 65 ++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 51 deletions(-) diff --git a/makepanda/installpanda.py b/makepanda/installpanda.py index 410847efc7..b53153c5d8 100644 --- a/makepanda/installpanda.py +++ b/makepanda/installpanda.py @@ -13,10 +13,6 @@ from distutils.sysconfig import get_python_lib from optparse import OptionParser from makepandacore import * -def python_sitepackages_path(): - from distutils.sysconfig import get_python_lib - return get_python_lib(1) -PYTHON_SITEPACKAGES=python_sitepackages_path() MIME_INFO = ( ("egg", "model/x-egg", "EGG model file", "pview"), @@ -153,18 +149,11 @@ def GetLibDir(): return "lib" -def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir()): +def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(), python_versions=[]): if (not prefix.startswith("/")): prefix = "/" + prefix libdir = prefix + "/" + libdir - # Determine the location of the Python executable and site-packages dir. - PPATH = get_python_lib(1) - if os.path.islink(sys.executable): - PEXEC = os.path.join(os.path.dirname(sys.executable), os.readlink(sys.executable)) - else: - PEXEC = sys.executable - # Create the directory structure that we will be putting our files in. oscmd("mkdir -m 0755 -p "+destdir+prefix+"/bin") oscmd("mkdir -m 0755 -p "+destdir+prefix+"/include") @@ -174,8 +163,10 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir( oscmd("mkdir -m 0755 -p "+destdir+prefix+"/share/application-registry") oscmd("mkdir -m 0755 -p "+destdir+prefix+"/share/applications") oscmd("mkdir -m 0755 -p "+destdir+libdir+"/panda3d") - oscmd("mkdir -m 0755 -p "+destdir+PPATH) - oscmd("mkdir -m 0755 -p "+destdir+PPATH+"/panda3d") + + for python_version in python_versions: + oscmd("mkdir -m 0755 -p "+destdir+python_version["purelib"]) + oscmd("mkdir -m 0755 -p "+destdir+python_version["platlib"]+"/panda3d") if (sys.platform.startswith("freebsd")): oscmd("mkdir -m 0755 -p "+destdir+prefix+"/etc") @@ -201,10 +192,12 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir( if os.path.isdir(outputdir+"/Pmw"): oscmd("cp -R "+outputdir+"/Pmw "+destdir+prefix+"/share/panda3d/") if os.path.isdir(outputdir+"/plugins"): oscmd("cp -R "+outputdir+"/plugins "+destdir+prefix+"/share/panda3d/") - suffix = GetExtensionSuffix() - for base in os.listdir(outputdir + "/panda3d"): - if base.endswith(".py") or (base.endswith(suffix) and '.' not in base[:-len(suffix)]): - oscmd("cp "+outputdir+"/panda3d/"+base+" "+destdir+PPATH+"/panda3d/"+base) + for python_version in python_versions: + for base in os.listdir(outputdir + "/panda3d"): + suffix = python_version["ext_suffix"] + platlib = python_version["platlib"] + if base.endswith(".py") or (base.endswith(suffix) and '.' not in base[:-len(suffix)]): + oscmd("cp "+outputdir+"/panda3d/"+base+" "+destdir+platlib+"/panda3d/"+base) WriteMimeFile(destdir+prefix+"/share/mime-info/panda3d.mime", MIME_INFO) WriteKeysFile(destdir+prefix+"/share/mime-info/panda3d.keys", MIME_INFO) @@ -214,8 +207,11 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir( oscmd("cp makepanda/pview.desktop "+destdir+prefix+"/share/applications/pview.desktop") oscmd("cp doc/ReleaseNotes "+destdir+prefix+"/share/panda3d/ReleaseNotes") - oscmd("echo '"+prefix+"/share/panda3d' > "+destdir+PPATH+"/panda3d.pth") - oscmd("echo '"+libdir+"/panda3d'>> "+destdir+PPATH+"/panda3d.pth") + + for python_version in python_versions: + pth_file = python_version["purelib"] + "/panda3d.pth" + oscmd("echo '"+prefix+"/share/panda3d' > "+destdir+pth_file) + if (sys.platform.startswith("freebsd")): oscmd("echo '"+libdir+"/panda3d'> "+destdir+"/usr/local/libdata/ldconfig/panda3d") else: @@ -300,6 +296,8 @@ if (__name__ == "__main__"): if (destdir != "" and not os.path.isdir(destdir)): exit("Directory '%s' does not exist!" % destdir) + SetOutputDir(options.outputdir) + if options.verbose: SetVerbose(True) @@ -308,6 +306,8 @@ if (__name__ == "__main__"): InstallRuntime(destdir = destdir, prefix = options.prefix, outputdir = options.outputdir) else: print("Installing Panda3D SDK into " + destdir + options.prefix) - InstallPanda(destdir = destdir, prefix = options.prefix, outputdir = options.outputdir) + InstallPanda(destdir=destdir, + prefix=options.prefix, + outputdir=options.outputdir, + python_versions=ReadPythonVersionInfoFile()) print("Installation finished!") - diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index dc3da45e19..f1148e1de8 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -16,9 +16,9 @@ Architecture: ARCH Essential: no Depends: DEPENDS Recommends: RECOMMENDS -Provides: panda3d, pythonPV-panda3d -Conflicts: panda3d, pythonPV-panda3d -Replaces: panda3d, pythonPV-panda3d +Provides: PROVIDES +Conflicts: PROVIDES +Replaces: PROVIDES Maintainer: rdb Installed-Size: INSTSIZE Description: Panda3D free 3D engine SDK @@ -73,7 +73,6 @@ This package contains the SDK for development with Panda3D, install panda3d-runt /usr/share/panda3d /etc/ld.so.conf.d/panda3d.conf /usr/%_lib/panda3d -""" + PYTHON_SITEPACKAGES + """ /usr/include/panda3d """ INSTALLER_SPEC_FILE_PVIEW = \ @@ -254,17 +253,28 @@ def MakeDebugSymbolArchive(zipname, dirname): zip.close() -def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs): +def MakeInstallerLinux(version, debversion=None, rpmrelease=1, runtime=False, + python_versions=[], **kwargs): outputdir = GetOutputDir() - if not runtime and not PkgSkip("PYTHON"): - if "PYTHONVERSION" in SDK: - PYTHONV = SDK["PYTHONVERSION"].rstrip('dmu') - else: - PYTHONV = "python%d.%d" % (sys.version_info[:2]) - else: - PYTHONV = "python" - PV = PYTHONV.replace("python", "") + # We pack Python 2 and Python 3, if we built with support for it. + python2_ver = None + python3_ver = None + install_python_versions = [] + + if not runtime: + # What's the system version of Python 3? + oscmd('python3 -V > "%s/tmp/python3_version.txt"' % (outputdir)) + sys_python3_ver = '.'.join(ReadFile(outputdir + "/tmp/python3_version.txt").strip().split(' ')[1].split('.')[:2]) + + # Check that we built with support for these. + for version_info in python_versions: + if version_info["version"] == "2.7": + python2_ver = "2.7" + install_python_versions.append(version_info) + elif version_info["version"] == sys_python3_ver: + python3_ver = sys_python3_ver + install_python_versions.append(version_info) major_version = '.'.join(version.split('.')[:2]) if not debversion: @@ -272,7 +282,7 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs): # Clean and set up a directory to install Panda3D into oscmd("rm -rf targetroot data.tar.gz control.tar.gz panda3d.spec") - oscmd("mkdir --mode=0755 targetroot") + oscmd("mkdir -m 0755 targetroot") dpkg_present = False if os.path.exists("/usr/bin/dpkg-architecture") and os.path.exists("/usr/bin/dpkg-deb"): @@ -288,9 +298,12 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs): # Invoke installpanda.py to install it into a temporary dir lib_dir = GetDebLibDir() if runtime: - InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=lib_dir) + InstallRuntime(destdir="targetroot", prefix="/usr", + outputdir=outputdir, libdir=lib_dir) else: - InstallPanda(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=lib_dir) + InstallPanda(destdir="targetroot", prefix="/usr", + outputdir=outputdir, libdir=lib_dir, + python_versions=install_python_versions) oscmd("chmod -R 755 targetroot/usr/share/panda3d") oscmd("mkdir -m 0755 -p targetroot/usr/share/man/man1") oscmd("install -m 0644 doc/man/*.1 targetroot/usr/share/man/man1/") @@ -301,9 +314,10 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs): txt = RUNTIME_INSTALLER_DEB_FILE[1:] else: txt = INSTALLER_DEB_FILE[1:] - txt = txt.replace("VERSION", debversion).replace("ARCH", pkg_arch).replace("PV", PV).replace("MAJOR", major_version) - txt = txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024)) - oscmd("mkdir --mode=0755 -p targetroot/DEBIAN") + txt = txt.replace("VERSION", debversion).replace("ARCH", pkg_arch).replace("MAJOR", major_version) + txt = txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") // 1024)) + + oscmd("mkdir -m 0755 -p targetroot/DEBIAN") oscmd("cd targetroot && (find usr -type f -exec md5sum {} ;) > DEBIAN/md5sums") if not runtime: oscmd("cd targetroot && (find etc -type f -exec md5sum {} ;) >> DEBIAN/md5sums") @@ -337,6 +351,7 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs): oscmd("cd targetroot && %(dpkg_shlibdeps)s -x%(pkg_name)s %(lib_pattern)s %(bin_pattern)s*" % locals()) depends = ReadFile("targetroot/debian/substvars").replace("shlibs:Depends=", "").strip() recommends = "" + provides = "panda3d-runtime" else: pkg_name = "panda3d" + major_version pkg_dir = "debian/panda3d" + major_version @@ -352,15 +367,29 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs): # Parse the substvars files generated by dpkg-shlibdeps. depends = ReadFile("targetroot/debian/substvars_dep").replace("shlibs:Depends=", "").strip() recommends = ReadFile("targetroot/debian/substvars_rec").replace("shlibs:Depends=", "").strip() - if PkgSkip("PYTHON")==0: - depends += ", " + PYTHONV - recommends += ", python-wxversion, python-profiler (>= " + PV + "), python-pmw, python-tk (>= " + PV + ")" - if PkgSkip("NVIDIACG")==0: + provides = "panda3d" + + if python2_ver or python3_ver: + recommends += ", python-pmw" + + if python2_ver: + depends += ", python%s" % (python2_ver) + recommends += ", python-wxversion" + recommends += ", python-tk (>= %s)" % (python2_ver) + provides += ", python2-panda3d" + + if python3_ver: + depends += ", python%s" % (python3_ver) + recommends += ", python3-tk (>= %s)" % (python3_ver) + provides += ", python3-panda3d" + + if not PkgSkip("NVIDIACG"): depends += ", nvidia-cg-toolkit" # Write back the dependencies, and delete the dummy set-up. txt = txt.replace("DEPENDS", depends.strip(', ')) txt = txt.replace("RECOMMENDS", recommends.strip(', ')) + txt = txt.replace("PROVIDES", provides.strip(', ')) WriteFile("targetroot/DEBIAN/control", txt) oscmd("rm -rf targetroot/debian") @@ -376,7 +405,9 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs): if runtime: InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=GetRPMLibDir()) else: - InstallPanda(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=GetRPMLibDir()) + InstallPanda(destdir="targetroot", prefix="/usr", + outputdir=outputdir, libdir=GetRPMLibDir(), + python_versions=install_python_versions) oscmd("chmod -R 755 targetroot/usr/share/panda3d") oscmd("rpm -E '%_target_cpu' > "+outputdir+"/tmp/architecture.txt") @@ -392,15 +423,23 @@ def MakeInstallerLinux(version, debversion=None, runtime=False, **kwargs): if not PkgSkip("PVIEW"): txt += INSTALLER_SPEC_FILE_PVIEW + # Add the platform-specific Python directories. + dirs = set() + for version_info in install_python_versions: + dirs.add(version_info["platlib"]) + dirs.add(version_info["purelib"]) + + for dir in dirs: + txt += dir + "\n" + # Add the binaries in /usr/bin explicitly to the spec file for base in os.listdir(outputdir + "/bin"): txt += "/usr/bin/%s\n" % (base) # Write out the spec file. txt = txt.replace("VERSION", version) - txt = txt.replace("RPMRELEASE", rpmrelease) + txt = txt.replace("RPMRELEASE", str(rpmrelease)) txt = txt.replace("PANDASOURCE", pandasource) - txt = txt.replace("PV", PV) WriteFile("panda3d.spec", txt) oscmd("fakeroot rpmbuild --define '_rpmdir "+pandasource+"' --buildroot '"+os.path.abspath("targetroot")+"' -bb panda3d.spec") @@ -693,7 +732,7 @@ def MakeInstallerFreeBSD(version, runtime=False, **kwargs): manifest_txt = manifest_txt.replace("ARCH", pkg_arch) manifest_txt = manifest_txt.replace("ORIGIN", 'devel/panda3d' if not runtime else 'graphics/panda3d-runtime') manifest_txt = manifest_txt.replace("DEPENDS", dependencies) - manifest_txt = manifest_txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024 / 1024)) + manifest_txt = manifest_txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") // 1024 // 1024)) WriteFile("pkg-plist", plist_txt) WriteFile("+DESC", INSTALLER_PKG_DESCR_FILE[1:] if not runtime else RUNTIME_INSTALLER_PKG_DESCR_FILE[1:]) @@ -961,4 +1000,5 @@ if __name__ == "__main__": compressor=options.compressor, debversion=options.debversion, rpmrelease=options.rpmrelease, - runtime=options.runtime) + runtime=options.runtime, + python_versions=ReadPythonVersionInfoFile()) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index cb9ab10e0c..f0f3c8de90 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6742,6 +6742,10 @@ if RUNTESTS: cmdstr += " --verbose" oscmd(cmdstr) +# Write out information about the Python versions in the built dir. +python_version_info = GetCurrentPythonVersionInfo() +UpdatePythonVersionInfoFile(python_version_info) + ########################################################################################## # # The Installers @@ -6755,10 +6759,16 @@ if RUNTESTS: if INSTALLER: ProgressOutput(100.0, "Building installer") from makepackage import MakeInstaller + + # When using the --installer flag, only install for the current version. + python_versions = [] + if python_version_info: + python_versions.append(python_version_info) + MakeInstaller(version=VERSION, outputdir=GetOutputDir(), optimize=GetOptimize(), compressor=COMPRESSOR, debversion=DEBVERSION, rpmrelease=RPMRELEASE, - runtime=RUNTIME) + runtime=RUNTIME, python_versions=python_versions) if WHEEL: ProgressOutput(100.0, "Building wheel") diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index 435b45a023..d4811c1120 100644 --- a/makepanda/makepandacore.py +++ b/makepanda/makepandacore.py @@ -3383,6 +3383,71 @@ def FindLocation(fn, ipath, pyabi=None): ORIG_EXT[loc] = ext return loc + +######################################################################## +## +## These files maintain a python_versions.json file in the built/tmp +## directory that can be used by the other scripts in this directory. +## +######################################################################## + + +def GetCurrentPythonVersionInfo(): + if PkgSkip("PYTHON"): + return + + from distutils.sysconfig import get_python_lib + return { + "version": SDK["PYTHONVERSION"][6:9], + "soabi": GetPythonABI(), + "ext_suffix": GetExtensionSuffix(), + "executable": sys.executable, + "purelib": get_python_lib(False), + "platlib": get_python_lib(True), + } + + +def UpdatePythonVersionInfoFile(new_info): + import json + + json_file = os.path.join(GetOutputDir(), "tmp", "python_versions.json") + json_data = [] + if os.path.isfile(json_file) and not PkgSkip("PYTHON"): + try: + json_data = json.load(open(json_file, 'r')) + except: + json_data = [] + + # Prune the list by removing the entries that conflict with our build, + # plus the entries that no longer exist + for version_info in json_data[:]: + core_pyd = os.path.join(GetOutputDir(), "panda3d", "core" + version_info["ext_suffix"]) + if version_info["ext_suffix"] == new_info["ext_suffix"] or \ + version_info["soabi"] == new_info["soabi"] or \ + not os.path.isfile(core_pyd): + json_data.remove(version_info) + + if not PkgSkip("PYTHON"): + json_data.append(new_info) + + if VERBOSE: + print("Writing %s" % (json_file)) + json.dump(json_data, open(json_file, 'w'), indent=4) + + +def ReadPythonVersionInfoFile(): + import json + + json_file = os.path.join(GetOutputDir(), "tmp", "python_versions.json") + if os.path.isfile(json_file): + try: + return json.load(open(json_file, 'r')) + except: + pass + + return [] + + ######################################################################## ## ## TargetAdd From 69decc17ff78c77ca7ec6a205ce8b045854830f9 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 23 Dec 2018 22:28:52 +0100 Subject: [PATCH 370/424] tests: raise GLSL version from 1.30 to 1.50 for compute shader --- tests/display/test_glsl_shader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/display/test_glsl_shader.py b/tests/display/test_glsl_shader.py index 0a0a5f5737..8deadf40d9 100644 --- a/tests/display/test_glsl_shader.py +++ b/tests/display/test_glsl_shader.py @@ -38,7 +38,7 @@ void main() {{ """ -def run_glsl_test(gsg, body, preamble="", inputs={}, version=130, exts=set()): +def run_glsl_test(gsg, body, preamble="", inputs={}, version=150, exts=set()): """ Runs a GLSL test on the given GSG. The given body is executed in the main function and should call assert(). The preamble should contain all of the shader inputs. """ From 8a38337e6bf390ed2ddab7afa8b964be547232af Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 24 Dec 2018 00:17:02 +0100 Subject: [PATCH 371/424] makepanda: fix model cache written in current dir on macOS This was a regression in 2b537d2263d312672fe4a380fb6f49bda641cd48 that caused the model-cache to be written to ./Library/Caches rather than $HOME/Library/Caches --- makepanda/makepanda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index f0f3c8de90..5172672380 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2934,7 +2934,7 @@ else: configprc = configprc.replace("aux-display pandadx9", "") if (GetTarget() == 'darwin'): - configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "Library/Caches/Panda3D-%s" % MAJOR_VERSION) + configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "$HOME/Library/Caches/Panda3D-%s" % MAJOR_VERSION) # OpenAL is not yet working well on OSX for us, so let's do this for now. configprc = configprc.replace("p3openal_audio", "p3fmod_audio") From 0fa0ad673f74ded96d6bb11378277b8e34529b29 Mon Sep 17 00:00:00 2001 From: Donny Lawrence Date: Mon, 24 Dec 2018 13:36:13 +0100 Subject: [PATCH 372/424] cocoa: use CVDisplayLink on macOS to sync video to display(s) Closes #487 Fixes #486 --- .../cocoadisplay/cocoaGraphicsStateGuardian.h | 8 +++ .../cocoaGraphicsStateGuardian.mm | 70 +++++++++++++++++-- panda/src/cocoadisplay/cocoaGraphicsWindow.h | 3 + panda/src/cocoadisplay/cocoaGraphicsWindow.mm | 13 ++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h index 2e83e60c34..8365834a16 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h @@ -20,6 +20,7 @@ #import #import +#import /** * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific @@ -38,14 +39,21 @@ public: CocoaGraphicsStateGuardian *share_with); virtual ~CocoaGraphicsStateGuardian(); + bool setup_vsync(); INLINE void lock_context(); INLINE void unlock_context(); NSOpenGLContext *_share_context; NSOpenGLContext *_context; + NSOpenGLPixelFormat *_format = nullptr; FrameBufferProperties _fbprops; + CVDisplayLinkRef _display_link = nullptr; + Mutex _swap_lock; + ConditionVar _swap_condition; + AtomicAdjust::Integer _last_wait_frame = 0; + protected: virtual void query_gl_version(); virtual void *do_get_extension_func(const char *name); diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm index d0dbd37c16..92f6226492 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm @@ -28,6 +28,20 @@ #define NSAppKitVersionNumber10_7 1138 #endif +/** + * Called whenever a display wants a frame. The context argument contains the + * applicable CocoaGraphicsStateGuardian. + */ +static CVReturn +display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now, + const CVTimeStamp* output_time, CVOptionFlags flags_in, + CVOptionFlags *flags_out, void *context) { + CocoaGraphicsStateGuardian *gsg = (CocoaGraphicsStateGuardian *)context; + MutexHolder swap_holder(gsg->_swap_lock); + gsg->_swap_condition.notify(); + return kCVReturnSuccess; +} + TypeHandle CocoaGraphicsStateGuardian::_type_handle; /** @@ -36,7 +50,8 @@ TypeHandle CocoaGraphicsStateGuardian::_type_handle; CocoaGraphicsStateGuardian:: CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe, CocoaGraphicsStateGuardian *share_with) : - GLGraphicsStateGuardian(engine, pipe) + GLGraphicsStateGuardian(engine, pipe), + _swap_condition(_swap_lock) { _share_context = nil; _context = nil; @@ -52,12 +67,59 @@ CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe, */ CocoaGraphicsStateGuardian:: ~CocoaGraphicsStateGuardian() { + if (_format != nil) { + [_format release]; + } + if (_display_link != nil) { + CVDisplayLinkRelease(_display_link); + _display_link = nil; + MutexHolder swap_holder(_swap_lock); + _swap_condition.notify(); + } if (_context != nil) { [_context clearDrawable]; [_context release]; } } +/** + * Creates a CVDisplayLink, which tells us when the display the window is on + * will want a frame. + */ +bool CocoaGraphicsStateGuardian:: +setup_vsync() { + if (_display_link != nil) { + // Already set up. + return true; + } + + CVReturn result = CVDisplayLinkCreateWithActiveCGDisplays(&_display_link); + if (result != kCVReturnSuccess) { + cocoadisplay_cat.error() << "Failed to create CVDisplayLink.\n"; + return false; + } + + result = CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_display_link, (CGLContextObj)[_context CGLContextObj], (CGLPixelFormatObj)[_format CGLPixelFormatObj]); + if (result != kCVReturnSuccess) { + cocoadisplay_cat.error() << "Failed to set CVDisplayLink's current display.\n"; + return false; + } + + result = CVDisplayLinkSetOutputCallback(_display_link, &display_link_cb, this); + if (result != kCVReturnSuccess) { + cocoadisplay_cat.error() << "Failed to set CVDisplayLink output callback.\n"; + return false; + } + + result = CVDisplayLinkStart(_display_link); + if (result != kCVReturnSuccess) { + cocoadisplay_cat.error() << "Failed to start the CVDisplayLink.\n"; + return false; + } + + return true; +} + /** * Gets the FrameBufferProperties to match the indicated config. */ @@ -262,15 +324,15 @@ choose_pixel_format(const FrameBufferProperties &properties, // TODO: print out renderer _context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:_share_context]; - [format release]; + _format = format; if (_context == nil) { cocoadisplay_cat.error() << "Failed to create OpenGL context!\n"; return; } - // Set vsync setting on the context - GLint swap = sync_video ? 1 : 0; + // Disable vsync via the built-in mechanism, which doesn't work on Mojave + GLint swap = 0; [_context setValues:&swap forParameter:NSOpenGLCPSwapInterval]; cocoadisplay_cat.debug() diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.h b/panda/src/cocoadisplay/cocoaGraphicsWindow.h index a49f748020..a053cb11b9 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.h +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.h @@ -23,6 +23,8 @@ #import #import +#import + /** * An interface to the Cocoa system for managing OpenGL windows under Mac OS * X. @@ -92,6 +94,7 @@ private: PT(GraphicsWindowInputDevice) _input; bool _mouse_hidden; bool _context_needs_update; + bool _vsync_enabled = false; #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 CGDisplayModeRef _fullscreen_mode; diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index df3ecf3b12..85e01917eb 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -274,6 +274,14 @@ end_flip() { CocoaGraphicsStateGuardian *cocoagsg; DCAST_INTO_V(cocoagsg, _gsg); + if (_vsync_enabled) { + AtomicAdjust::Integer cur_frame = ClockObject::get_global_clock()->get_frame_count(); + if (AtomicAdjust::set(cocoagsg->_last_wait_frame, cur_frame) != cur_frame) { + MutexHolder swap_holder(cocoagsg->_swap_lock); + cocoagsg->_swap_condition.wait(); + } + } + cocoagsg->lock_context(); // Swap the front and back buffer. @@ -669,6 +677,8 @@ open_window() { mouse_mode_relative(); } + _vsync_enabled = sync_video && cocoagsg->setup_vsync(); + return true; } @@ -710,6 +720,8 @@ close_window() { _view = nil; } + _vsync_enabled = false; + GraphicsWindow::close_window(); } @@ -1491,6 +1503,7 @@ handle_close_event() { cocoagsg->unlock_context(); } _gsg.clear(); + _vsync_enabled = false; } // Dump the view, too From d180be2acc9316269e715a106e457a62fce535a6 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 24 Dec 2018 13:49:41 +0100 Subject: [PATCH 373/424] makepackage: build macOS installer for multiple Python versions See #441 --- makepanda/makepackage.py | 112 ++++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 31 deletions(-) diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index f1148e1de8..ce7282626e 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -453,7 +453,7 @@ def MakeInstallerLinux(version, debversion=None, rpmrelease=1, runtime=False, exit("To build an installer, either rpmbuild or dpkg-deb must be present on your system!") -def MakeInstallerOSX(version, runtime=False, **kwargs): +def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs): outputdir = GetOutputDir() if runtime: @@ -467,17 +467,11 @@ def MakeInstallerOSX(version, runtime=False, **kwargs): oscmd(cmdstr) return - if "PYTHONVERSION" in SDK: - pyver = SDK["PYTHONVERSION"][6:9] - else: - pyver = "%d.%d" % (sys.version_info[:2]) - dmg_name = "Panda3D-" + version - if not pyver.startswith("2."): + if len(python_versions) == 1 and not python_versions[0]["version"].startswith("2."): dmg_name += "-py" + pyver dmg_name += ".dmg" - import compileall if (os.path.isfile(dmg_name)): oscmd("rm -f %s" % dmg_name) if (os.path.exists("dstroot")): oscmd("rm -rf dstroot") if (os.path.exists("Panda3D-rw.dmg")): oscmd('rm -f Panda3D-rw.dmg') @@ -518,33 +512,43 @@ def MakeInstallerOSX(version, runtime=False, **kwargs): # OSX needs the -R argument to copy symbolic links correctly, it doesn't have -d. How weird. oscmd("cp -R " + outputdir + "/bin/" + base + " " + binname) - if not PkgSkip("PYTHON"): - pyexec = SDK.get("PYTHONEXEC", sys.executable) - oscmd("mkdir -p dstroot/pythoncode/usr/local/bin") + if python_versions: + # Let's only write a ppython link if there is only one Python version. + if len(python_versions) == 1: + oscmd("mkdir -p dstroot/pythoncode/usr/local/bin") + oscmd("ln -s %s dstroot/pythoncode/usr/local/bin/ppython" % (python_versions[0]["executable"])) + oscmd("mkdir -p dstroot/pythoncode/Developer/Panda3D/panda3d") - oscmd("mkdir -p dstroot/pythoncode/Library/Python/%s/site-packages" % pyver) - WriteFile("dstroot/pythoncode/Library/Python/%s/site-packages/Panda3D.pth" % pyver, "/Developer/Panda3D") oscmd("cp -R %s/pandac dstroot/pythoncode/Developer/Panda3D/pandac" % outputdir) oscmd("cp -R %s/direct dstroot/pythoncode/Developer/Panda3D/direct" % outputdir) - oscmd("ln -s %s dstroot/pythoncode/usr/local/bin/ppython" % pyexec) oscmd("cp -R %s/*.so dstroot/pythoncode/Developer/Panda3D/" % outputdir, True) oscmd("cp -R %s/*.py dstroot/pythoncode/Developer/Panda3D/" % outputdir, True) if os.path.isdir(outputdir+"/Pmw"): oscmd("cp -R %s/Pmw dstroot/pythoncode/Developer/Panda3D/Pmw" % outputdir) - compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/Pmw") - WriteFile("dstroot/pythoncode/Developer/Panda3D/direct/__init__.py", "") - for base in os.listdir("dstroot/pythoncode/Developer/Panda3D/direct"): - if ((base != "extensions") and (base != "extensions_native")): - compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/direct/"+base) - suffix = GetExtensionSuffix() for base in os.listdir(outputdir+"/panda3d"): - if base.endswith('.py') or (base.endswith(suffix) and '.' not in base[:-len(suffix)]): + if base.endswith('.py'): libname = "dstroot/pythoncode/Developer/Panda3D/panda3d/" + base + oscmd("cp -R " + outputdir + "/panda3d/" + base + " " + libname) + + for version_info in python_versions: + pyver = version_info["version"] + oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver)) + oscmd("mkdir -p dstroot/pybindings%s/Developer/Panda3D/panda3d" % (pyver)) + + # Copy over extension modules. + suffix = version_info["ext_suffix"] + for base in os.listdir(outputdir+"/panda3d"): + if base.endswith(suffix) and '.' not in base[:-len(suffix)]: + libname = "dstroot/pybindings%s/Developer/Panda3D/panda3d/%s" % (pyver, base) # We really need to specify -R in order not to follow symlinks # On OSX, just specifying -P is not enough to do that. oscmd("cp -R -P " + outputdir + "/panda3d/" + base + " " + libname) + # Write a .pth file. + oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver)) + WriteFile("dstroot/pybindings%s/Library/Python/%s/site-packages/Panda3D.pth" % (pyver, pyver), "/Developer/Panda3D") + if not PkgSkip("FFMPEG"): oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib") oscmd("cp -R %s/lib/libp3ffmpeg.* dstroot/ffmpeg/Developer/Panda3D/lib/" % outputdir) @@ -567,9 +571,19 @@ def MakeInstallerOSX(version, runtime=False, **kwargs): oscmd("mkdir -p dstroot/samples/Developer/Examples/Panda3D") oscmd("cp -R samples/* dstroot/samples/Developer/Examples/Panda3D/") - oscmd("chmod -R 0775 dstroot/*") DeleteVCS("dstroot") DeleteBuildFiles("dstroot") + + # Compile Python files. Do this *after* the DeleteVCS step, above, which + # deletes __pycache__ directories. + for version_info in python_versions: + if os.path.isdir("dstroot/pythoncode/Developer/Panda3D/Pmw"): + oscmd("%s -m compileall -q -f -d /Developer/Panda3D/Pmw dstroot/pythoncode/Developer/Panda3D/Pmw" % (version_info["executable"]), True) + oscmd("%s -m compileall -q -f -d /Developer/Panda3D/direct dstroot/pythoncode/Developer/Panda3D/direct" % (version_info["executable"])) + oscmd("%s -m compileall -q -f -d /Developer/Panda3D/pandac dstroot/pythoncode/Developer/Panda3D/pandac" % (version_info["executable"])) + oscmd("%s -m compileall -q -f -d /Developer/Panda3D/panda3d dstroot/pythoncode/Developer/Panda3D/panda3d" % (version_info["executable"])) + + oscmd("chmod -R 0775 dstroot/*") # We need to be root to perform a chown. Bleh. # Fortunately PackageMaker does it for us, on 10.5 and above. #oscmd("chown -R root:admin dstroot/*", True) @@ -578,7 +592,10 @@ def MakeInstallerOSX(version, runtime=False, **kwargs): oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Resources/en.lproj/") pkgs = ["base", "tools", "headers"] - if not PkgSkip("PYTHON"): pkgs.append("pythoncode") + if python_versions: + pkgs.append("pythoncode") + for version_info in python_versions: + pkgs.append("pybindings" + version_info["version"]) if not PkgSkip("FFMPEG"): pkgs.append("ffmpeg") #if not PkgSkip("OPENAL"): pkgs.append("openal") if not PkgSkip("FMODEX"): pkgs.append("fmodex") @@ -620,22 +637,55 @@ def MakeInstallerOSX(version, runtime=False, **kwargs): dist.write(' Panda3D SDK %s\n' % (version)) dist.write(' \n') dist.write(' %s\n' % ReadFile("doc/LICENSE")) + dist.write(' \n') dist.write(' \n') - for pkg in pkgs: - dist.write(' \n' % (pkg)) + dist.write(' \n') + if python_versions: + dist.write(' \n') + for version_info in python_versions: + dist.write(' \n' % (version_info["version"])) + dist.write(' \n') + dist.write(' \n') + if os.path.isdir("samples"): + dist.write(' \n') + if not PkgSkip("FFMPEG"): + dist.write(' \n') + if not PkgSkip("FMODEX"): + dist.write(' \n') + dist.write(' \n') dist.write(' \n') - dist.write(' \n') + dist.write(' \n') dist.write(' \n') dist.write(' \n') - dist.write(' \n') + dist.write(' \n') dist.write(' \n') dist.write(' \n') - if not PkgSkip("PYTHON"): - dist.write(' \n') + if python_versions: + dist.write(' \n') dist.write(' \n') dist.write(' \n') + for version_info in python_versions: + pyver = version_info["version"] + if pyver == "2.7": + # Always install Python 2.7 by default; it's included on macOS. + cond = "true" + else: + cond = "isPythonVersionInstalled('%s')" % (pyver) + dist.write(' \n' % (pyver, cond, pyver, pyver)) + dist.write(' \n' % (pyver)) + dist.write(' \n') + if not PkgSkip("FFMPEG"): dist.write(' \n') if os.path.isdir("samples"): - dist.write(' \n') + dist.write(' \n') dist.write(' \n') dist.write(' \n') - dist.write(' \n') + dist.write(' \n') dist.write(' \n') dist.write(' \n') for pkg in pkgs: From 94d1722c8c29e304798119e875fb328a7829a3d2 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 24 Dec 2018 20:51:20 +0100 Subject: [PATCH 374/424] makepanda: add --no-copy-python flag for Windows build This will avoid copying over the python installation to the built/python directory. --- makepanda/makepanda.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 5172672380..b03ca4d4d9 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -70,6 +70,7 @@ WINDOWS_SDK = None MSVC_VERSION = None BOOUSEINTELCOMPILER = False OPENCV_VER_23 = False +COPY_PYTHON = True if "MACOSX_DEPLOYMENT_TARGET" in os.environ: OSXTARGET=os.environ["MACOSX_DEPLOYMENT_TARGET"] @@ -170,6 +171,7 @@ def parseopts(args): global COMPRESSOR,THREADCOUNT,OSXTARGET,OSX_ARCHS,HOST_URL global DEBVERSION,WHLVERSION,RPMRELEASE,GIT_COMMIT,P3DSUFFIX,RTDIST_VERSION global STRDXSDKVERSION, WINDOWS_SDK, MSVC_VERSION, BOOUSEINTELCOMPILER + global COPY_PYTHON # Options for which to display a deprecation warning. removedopts = [ @@ -183,7 +185,7 @@ def parseopts(args): "version=","lzma","no-python","threads=","outputdir=","override=", "static","host=","debversion=","rpmrelease=","p3dsuffix=","rtdist-version=", "directx-sdk=", "windows-sdk=", "msvc-version=", "clean", "use-icl", - "universal", "target=", "arch=", "git-commit=", + "universal", "target=", "arch=", "git-commit=", "no-copy-python", ] + removedopts anything = 0 @@ -249,6 +251,7 @@ def parseopts(args): MSVC_VERSION = value.strip().lower() elif (option=="--use-icl"): BOOUSEINTELCOMPILER = True elif (option=="--clean"): clean_build = True + elif (option=="--no-copy-python"): COPY_PYTHON = False elif (option[2:] in removedopts): Warn("Ignoring removed option %s" % (option)) else: @@ -3058,7 +3061,8 @@ if tp_dir is not None: CopyFile(GetOutputDir() + "/bin/", fn) # Copy the whole Python directory. - CopyTree(GetOutputDir() + "/python", SDK["PYTHON"]) + if COPY_PYTHON: + CopyTree(GetOutputDir() + "/python", SDK["PYTHON"]) # NB: Python does not always ship with the correct manifest/dll. # Figure out the correct one to ship, and grab it from WinSxS dir. @@ -3067,7 +3071,7 @@ if tp_dir is not None: os.unlink(manifest) oscmd('mt -inputresource:"%s\\python.exe";#1 -out:"%s" -nologo' % (SDK["PYTHON"], manifest), True) - if os.path.isfile(manifest): + if COPY_PYTHON and os.path.isfile(manifest): import xml.etree.ElementTree as ET tree = ET.parse(manifest) idents = tree.findall('./{urn:schemas-microsoft-com:asm.v1}dependency/{urn:schemas-microsoft-com:asm.v1}dependentAssembly/{urn:schemas-microsoft-com:asm.v1}assemblyIdentity') @@ -3097,11 +3101,12 @@ if tp_dir is not None: CopyFile(GetOutputDir() + "/python/", file) # Copy python.exe to ppython.exe. - if not os.path.isfile(SDK["PYTHON"] + "/ppython.exe") and os.path.isfile(SDK["PYTHON"] + "/python.exe"): - CopyFile(GetOutputDir() + "/python/ppython.exe", SDK["PYTHON"] + "/python.exe") - if not os.path.isfile(SDK["PYTHON"] + "/ppythonw.exe") and os.path.isfile(SDK["PYTHON"] + "/pythonw.exe"): - CopyFile(GetOutputDir() + "/python/ppythonw.exe", SDK["PYTHON"] + "/pythonw.exe") - ConditionalWriteFile(GetOutputDir() + "/python/panda.pth", "..\n../bin\n") + if COPY_PYTHON: + if not os.path.isfile(SDK["PYTHON"] + "/ppython.exe") and os.path.isfile(SDK["PYTHON"] + "/python.exe"): + CopyFile(GetOutputDir() + "/python/ppython.exe", SDK["PYTHON"] + "/python.exe") + if not os.path.isfile(SDK["PYTHON"] + "/ppythonw.exe") and os.path.isfile(SDK["PYTHON"] + "/pythonw.exe"): + CopyFile(GetOutputDir() + "/python/ppythonw.exe", SDK["PYTHON"] + "/pythonw.exe") + ConditionalWriteFile(GetOutputDir() + "/python/panda.pth", "..\n../bin\n") # Copy over the MSVC runtime. if GetTarget() == 'windows' and "VISUALSTUDIO" in SDK: From 950ffd6eb8f17e349eeb7694585d568a0382d9df Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Mon, 24 Dec 2018 12:05:34 -0800 Subject: [PATCH 375/424] deploy-ng: Add https://archive.panda3d.org/simple/opt as an optimized wheel index for release builds Fixes #469 --- direct/src/dist/commands.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index 1ee2527950..ec69bf996f 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -315,11 +315,18 @@ class build_apps(setuptools.Command): if self.use_optimized_wheels: if not self.optimized_wheel_index: # Try to find an appropriate wheel index + + # Start with the release index + self.optimized_wheel_index = 'https://archive.panda3d.org/simple/opt' + + # See if a buildbot build is being used with open(self.requirements_path) as reqsfile: reqsdata = reqsfile.read() matches = re.search(r'--extra-index-url (https*://archive.panda3d.org/.*\b)', reqsdata) if matches and matches.group(1): - self.optimized_wheel_index = matches.group(1) + '/opt' + self.optimized_wheel_index = matches.group(1) + if not matches.group(1).endswith('opt'): + self.optimized_wheel_index += '/opt' assert self.optimized_wheel_index, 'An index for optimized wheels must be defined if use_optimized_wheels is set' From 862906b86d4a363c9d52afc00a5c6527bb07d5b8 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 24 Dec 2018 21:38:46 +0100 Subject: [PATCH 376/424] makepackage: install for multiple Python versions on Windows See #441 --- makepanda/installer.nsi | 696 ++++++++++++++++++++++----------------- makepanda/makepackage.py | 34 +- 2 files changed, 416 insertions(+), 314 deletions(-) diff --git a/makepanda/installer.nsi b/makepanda/installer.nsi index 8bc608cf6f..9abb029cac 100644 --- a/makepanda/installer.nsi +++ b/makepanda/installer.nsi @@ -14,7 +14,7 @@ ; ; BUILT - location of panda install tree. ; SOURCE - location of the panda source-tree if available, OR location of panda install tree. -; PYVER - version of Python that Panda was built with (ie, "2.7") +; INCLUDE_PYVER - version of Python that Panda was built with (eg. "2.7", "3.5-32") ; REGVIEW - either 32 or 64, depending on the build architecture. ; @@ -46,7 +46,9 @@ SetCompressor ${COMPRESSOR} !insertmacro MUI_PAGE_LICENSE "${SOURCE}/doc/LICENSE" !insertmacro MUI_PAGE_DIRECTORY +!ifdef INCLUDE_PYVER !define MUI_PAGE_CUSTOMFUNCTION_LEAVE ConfirmPythonSelection +!endif !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_INSTFILES @@ -64,8 +66,9 @@ ShowUninstDetails hide LicenseData "${SOURCE}/doc/LICENSE" -InstType "Full (Recommended)" -InstType "Minimal" +InstType "Auto (Recommended)" +InstType "Full" +InstType "Light" LangString DESC_SecCore ${LANG_ENGLISH} "The Panda3D core libraries, configuration files and models/textures that are needed to use Panda3D." LangString DESC_SecOpenGL ${LANG_ENGLISH} "The OpenGL graphics back-end is the most well-supported renderer." @@ -78,8 +81,9 @@ LangString DESC_SecODE ${LANG_ENGLISH} "Support for the Open Dynamics Engine to LangString DESC_SecPhysX ${LANG_ENGLISH} "Support for NVIDIA PhysX to implement physics." LangString DESC_SecRocket ${LANG_ENGLISH} "Support for the libRocket GUI library. This is an optional library that offers an HTML/CSS-like approach to creating user interfaces." LangString DESC_SecTools ${LANG_ENGLISH} "Useful tools and model converters to help with Panda3D development. Recommended." -LangString DESC_SecPyBindings ${LANG_ENGLISH} "Contains the Python modules that allow use of Panda3D using Python. These will only work with a ${REGVIEW}-bit version of Python ${PYVER}." -LangString DESC_SecPython ${LANG_ENGLISH} "Contains a ${REGVIEW}-bit copy of Python ${PYVER} preconfigured to make use of Panda3D." +LangString DESC_SecGroupPython ${LANG_ENGLISH} "Contains modules that provide Python support for Panda3D." +LangString DESC_SecPyShared ${LANG_ENGLISH} "Contains the common Python code used by the Panda3D Python bindings." +LangString DESC_SecPython ${LANG_ENGLISH} "Contains a ${REGVIEW}-bit copy of Python ${INCLUDE_PYVER} preconfigured to make use of Panda3D." LangString DESC_SecHeadersLibs ${LANG_ENGLISH} "Headers and libraries needed for C++ development with Panda3D." LangString DESC_SecSamples ${LANG_ENGLISH} "The sample programs demonstrate how to make Python applications with Panda3D." LangString DESC_SecMaxPlugins ${LANG_ENGLISH} "Plug-ins for Autodesk 3ds Max (${REGVIEW}-bit) that can be used to export models to Panda3D." @@ -112,224 +116,38 @@ var MANPAGE !insertmacro !defineifexist HAVE_ODE "${BUILT}\bin\libpandaode.dll" !insertmacro !defineifexist HAVE_PHYSX "${BUILT}\bin\libpandaphysx.dll" !insertmacro !defineifexist HAVE_ROCKET "${BUILT}\bin\libp3rocket.dll" -!insertmacro !defineifexist HAVE_PYTHON "${BUILT}\python" !insertmacro !defineifexist HAVE_SAMPLES "${SOURCE}\samples" !insertmacro !defineifexist HAVE_MAX_PLUGINS "${BUILT}\plugins\*.dlo" !insertmacro !defineifexist HAVE_MAYA_PLUGINS "${BUILT}\plugins\*.mll" -Function runFunction - ExecShell "open" "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk" -FunctionEnd +!macro RemovePythonPath PYVER + ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" "" + StrCmp $0 "$INSTDIR" 0 +2 + DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" +!macroend -Function .onInit - ${If} ${REGVIEW} = 64 - ${AndIfNot} ${RunningX64} - MessageBox MB_OK|MB_ICONEXCLAMATION "You are attempting to install the 64-bit version of Panda3D on a 32-bit version of Windows. Please download and install the 32-bit version of Panda3D instead." - Abort - ${EndIf} -FunctionEnd +!macro PyBindingSection PYVER EXT_SUFFIX + LangString DESC_SecPyBindings${PYVER} ${LANG_ENGLISH} "Contains the Python modules that allow use of Panda3D using a ${REGVIEW}-bit version of Python ${PYVER}." -SectionGroup "Panda3D Libraries" - Section "Core Libraries" SecCore - SectionIn 1 2 RO - - SetShellVarContext current - SetOverwrite try + !insertmacro !defineifexist _present "${BUILT}\panda3d\core${EXT_SUFFIX}" + !ifdef _present + Section "${PYVER} bindings" SecPyBindings${PYVER} + !if "${PYVER}" == "${INCLUDE_PYVER}" + SectionIn 1 2 3 + !else + !if "${PYVER}" == "2.7" + SectionIn 1 2 + !else + ; See .onInit function where this is dynamically enabled. + SectionIn 2 + !endif + !endif SetDetailsPrint both - DetailPrint "Installing Panda3D libraries..." + DetailPrint "Installing Panda3D bindings for Python ${PYVER}..." SetDetailsPrint listonly - SetOutPath "$INSTDIR" - File /nonfatal "${BUILT}\LICENSE" - File /nonfatal "${BUILT}\ReleaseNotes" - File /nonfatal "${BUILT}\pandaIcon.ico" - - SetOutPath $INSTDIR\etc - File /r "${BUILT}\etc\*" - - SetOutPath $INSTDIR\bin - File /r /x api-ms-win-*.dll /x ucrtbase.dll /x libpandagl.dll /x libpandadx9.dll /x cgD3D*.dll /x python*.dll /x libpandaode.dll /x libp3fmod_audio.dll /x fmodex*.dll /x libp3ffmpeg.dll /x av*.dll /x postproc*.dll /x swscale*.dll /x swresample*.dll /x NxCharacter*.dll /x cudart*.dll /x PhysX*.dll /x libpandaphysx.dll /x libp3rocket.dll /x boost_python*.dll /x Rocket*.dll /x _rocket*.pyd /x libpandabullet.dll /x OpenAL32.dll /x *_oal.dll /x libp3openal_audio.dll "${BUILT}\bin\*.dll" - File /nonfatal /r "${BUILT}\bin\Microsoft.*.manifest" - - ; Before Windows 10, we need these stubs for the UCRT as well. - ReadRegDWORD $0 HKLM "Software\Microsoft\Windows NT\CurrentVersion" "CurrentMajorVersionNumber" - ${If} $0 < 10 - ClearErrors - File /nonfatal /r "${BUILT}\bin\api-ms-win-*.dll" - File /nonfatal "${BUILT}\bin\ucrtbase.dll" - ${Endif} - - SetDetailsPrint both - DetailPrint "Installing models..." - SetDetailsPrint listonly - - SetOutPath $INSTDIR\models - File /r /x CVS "${BUILT}\models\*" - - SetDetailsPrint both - DetailPrint "Installing optional components..." - SetDetailsPrint listonly - - RMDir /r "$SMPROGRAMS\${TITLE}" - CreateDirectory "$SMPROGRAMS\${TITLE}" - SectionEnd - - !ifdef HAVE_GL - Section "OpenGL" SecOpenGL - SectionIn 1 2 RO - - SetOutPath "$INSTDIR\bin" - File "${BUILT}\bin\libpandagl.dll" - SectionEnd - !endif - - !ifdef HAVE_DX9 - Section "Direct3D 9" SecDirect3D9 - SectionIn 1 - - SetOutPath "$INSTDIR\bin" - File "${BUILT}\bin\libpandadx9.dll" - File /nonfatal /r "${BUILT}\bin\cgD3D9.dll" - SectionEnd - !endif - - !ifdef HAVE_OPENAL - Section "OpenAL Audio" SecOpenAL - SectionIn 1 2 - - SetOutPath "$INSTDIR\bin" - File "${BUILT}\bin\libp3openal_audio.dll" - File /nonfatal /r "${BUILT}\bin\OpenAL32.dll" - File /nonfatal /r "${BUILT}\bin\*_oal.dll" - SectionEnd - !endif - - !ifdef HAVE_FMOD - Section "FMOD Audio" SecFMOD - SectionIn 1 - - SetOutPath "$INSTDIR\bin" - File "${BUILT}\bin\libp3fmod_audio.dll" - File /r "${BUILT}\bin\fmodex*.dll" - SectionEnd - !endif - - !ifdef HAVE_FFMPEG - Section "FFMpeg" SecFFMpeg - SectionIn 1 - - SetOutPath "$INSTDIR\bin" - File "${BUILT}\bin\libp3ffmpeg.dll" - File /nonfatal /r "${BUILT}\bin\av*.dll" - File /nonfatal /r "${BUILT}\bin\swscale*.dll" - File /nonfatal /r "${BUILT}\bin\swresample*.dll" - File /nonfatal /r "${BUILT}\bin\postproc*.dll" - SectionEnd - !endif - - !ifdef HAVE_BULLET - Section "Bullet Physics" SecBullet - SectionIn 1 - - SetOutPath "$INSTDIR\bin" - File "${BUILT}\bin\libpandabullet.dll" - SectionEnd - !endif - - !ifdef HAVE_ODE - Section "ODE Physics" SecODE - SectionIn 1 - - SetOutPath "$INSTDIR\bin" - File "${BUILT}\bin\libpandaode.dll" - SectionEnd - !endif - - !ifdef HAVE_PHYSX - Section "NVIDIA PhysX" SecPhysX - SectionIn 1 - - SetOutPath "$INSTDIR\bin" - File "${BUILT}\bin\libpandaphysx.dll" - File /nonfatal /r "${BUILT}\bin\PhysX*.dll" - File /nonfatal /r "${BUILT}\bin\NxCharacter*.dll" - File /nonfatal /r "${BUILT}\bin\cudart*.dll" - SectionEnd - !endif - - !ifdef HAVE_ROCKET - Section "libRocket GUI" SecRocket - SectionIn 1 - - SetOutPath "$INSTDIR\bin" - File "${BUILT}\bin\libp3rocket.dll" - File /nonfatal /r "${BUILT}\bin\Rocket*.dll" - File /nonfatal /r "${BUILT}\bin\_rocket*.pyd" - File /nonfatal /r "${BUILT}\bin\boost_python*.dll" - SectionEnd - !endif -SectionGroupEnd - -Section "Tools and utilities" SecTools - SectionIn 1 2 - - SetDetailsPrint both - DetailPrint "Installing utilities..." - SetDetailsPrint listonly - - SetOutPath "$INSTDIR\bin" - File /r "${BUILT}\bin\*.exe" - File /nonfatal /r "${BUILT}\bin\*.p3d" - SetOutPath "$INSTDIR\NSIS" - File /r /x CVS "${NSISDIR}\*" - - WriteRegStr HKCU "Software\Classes\Panda3D.Model" "" "Panda3D model/animation" - WriteRegStr HKCU "Software\Classes\Panda3D.Model\DefaultIcon" "" "$INSTDIR\bin\pview.exe" - WriteRegStr HKCU "Software\Classes\Panda3D.Model\shell" "" "open" - WriteRegStr HKCU "Software\Classes\Panda3D.Model\shell\open\command" "" '"$INSTDIR\bin\pview.exe" -l "%1"' - WriteRegStr HKCU "Software\Classes\Panda3D.Model\shell\compress" "" "Compress to .pz" - WriteRegStr HKCU "Software\Classes\Panda3D.Model\shell\compress\command" "" '"$INSTDIR\bin\pzip.exe" "%1"' - - WriteRegStr HKCU "Software\Classes\Panda3D.Compressed" "" "Compressed file" - WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\DefaultIcon" "" "$INSTDIR\bin\pzip.exe" - WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\shell" "" "open" - WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\shell\open\command" "" '"$INSTDIR\bin\pview.exe" -l "%1"' - WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\shell\decompress" "" "Decompress" - WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\shell\decompress\command" "" '"$INSTDIR\bin\punzip.exe" "%1"' - - WriteRegStr HKCU "Software\Classes\Panda3D.Multifile" "" "Panda3D Multifile" - WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\DefaultIcon" "" "$INSTDIR\bin\multify.exe" - WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell" "" "open" - WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract" "" "Extract here" - WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract\command" "" '"$INSTDIR\bin\multify.exe" -xf "%1"' -SectionEnd - -SectionGroup "Python support" - Section "Python bindings" SecPyBindings - SectionIn 1 2 - - SetDetailsPrint both - DetailPrint "Installing Panda3D Python modules..." - SetDetailsPrint listonly - - SetOutPath "$INSTDIR\bin" - File /nonfatal /r "${BUILT}\bin\*.pyd" - - SetOutPath $INSTDIR\direct\directscripts - File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\directscripts\*" - SetOutPath $INSTDIR\direct\filter - File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\filter\*.sha" - SetOutPath $INSTDIR\direct - File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\*.py" - - Delete "$INSTDIR\panda3d.py" - Delete "$INSTDIR\panda3d.pyc" - Delete "$INSTDIR\panda3d.pyo" - SetOutPath $INSTDIR\pandac - File /r "${BUILT}\pandac\*.py" SetOutPath $INSTDIR\panda3d - File /r "${BUILT}\panda3d\*.py" File /nonfatal /r "${BUILT}\panda3d\core${EXT_SUFFIX}" File /nonfatal /r "${BUILT}\panda3d\ai${EXT_SUFFIX}" @@ -384,87 +202,344 @@ SectionGroup "Python support" SetRegView ${REGVIEW} !endif - ; Check for a non-Panda3D system-wide Python installation. - ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" "" - StrCmp $0 "$INSTDIR\python" UserExternalPthCheck 0 - StrCmp $0 "" UserExternalPthCheck 0 - IfFileExists "$0\ppython.exe" UserExternalPthCheck 0 - IfFileExists "$0\python.exe" AskExternalPth UserExternalPthCheck - - ; Check for a non-Panda3D user installation of Python. - UserExternalPthCheck: - ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" "" - StrCmp $0 "$INSTDIR\python" SkipExternalPth 0 - StrCmp $0 "" SkipExternalPth 0 - IfFileExists "$0\ppython.exe" SkipExternalPth 0 - IfFileExists "$0\python.exe" AskExternalPth SkipExternalPth - - ; We're pretty sure this Python build is of the right architecture. - AskExternalPth: - MessageBox MB_YESNO|MB_ICONQUESTION \ - "Your system already has a copy of Python ${PYVER} installed in:$\r$\n$0$\r$\nWould you like to configure it to be able to use the Panda3D libraries?$\r$\nIf you choose no, you will only be able to use Panda3D's own copy of Python." \ - IDYES WriteExternalPth IDNO SkipExternalPth - - WriteExternalPth: - ;FileOpen $1 "$0\Lib\site-packages\panda.pth" w - ;FileWrite $1 "$INSTDIR$\r$\n" - ;FileWrite $1 "$INSTDIR\bin$\r$\n" - ;FileClose $1 - - ; Actually, it looks like we can just do this instead: + ; Install a Panda3D path into the global PythonPath for this version + ; of Python. WriteRegStr HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" "" "$INSTDIR" + SectionEnd + !undef _present + !endif +!macroend - SkipExternalPth: +Function runFunction + ExecShell "open" "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk" +FunctionEnd + +SectionGroup "Panda3D Libraries" + Section "Core Libraries" SecCore + SectionIn 1 2 3 RO + + SetShellVarContext current + SetOverwrite try + + SetDetailsPrint both + DetailPrint "Installing Panda3D libraries..." + SetDetailsPrint listonly + + SetOutPath "$INSTDIR" + File /nonfatal "${BUILT}\LICENSE" + File /nonfatal "${BUILT}\ReleaseNotes" + File /nonfatal "${BUILT}\pandaIcon.ico" + + SetOutPath $INSTDIR\etc + File /r "${BUILT}\etc\*" + + SetOutPath $INSTDIR\bin + File /r /x api-ms-win-*.dll /x ucrtbase.dll /x libpandagl.dll /x libpandadx9.dll /x cgD3D*.dll /x python*.dll /x libpandaode.dll /x libp3fmod_audio.dll /x fmodex*.dll /x libp3ffmpeg.dll /x av*.dll /x postproc*.dll /x swscale*.dll /x swresample*.dll /x NxCharacter*.dll /x cudart*.dll /x PhysX*.dll /x libpandaphysx.dll /x libp3rocket.dll /x boost_python*.dll /x Rocket*.dll /x _rocket*.pyd /x libpandabullet.dll /x OpenAL32.dll /x *_oal.dll /x libp3openal_audio.dll "${BUILT}\bin\*.dll" + File /nonfatal /r "${BUILT}\bin\Microsoft.*.manifest" + + ; Before Windows 10, we need these stubs for the UCRT as well. + ReadRegDWORD $0 HKLM "Software\Microsoft\Windows NT\CurrentVersion" "CurrentMajorVersionNumber" + ${If} $0 < 10 + ClearErrors + File /nonfatal /r "${BUILT}\bin\api-ms-win-*.dll" + File /nonfatal "${BUILT}\bin\ucrtbase.dll" + ${Endif} + + SetDetailsPrint both + DetailPrint "Installing models..." + SetDetailsPrint listonly + + SetOutPath $INSTDIR\models + File /r /x CVS "${BUILT}\models\*" + + SetDetailsPrint both + DetailPrint "Installing optional components..." + SetDetailsPrint listonly + + RMDir /r "$SMPROGRAMS\${TITLE}" + CreateDirectory "$SMPROGRAMS\${TITLE}" SectionEnd - !ifdef HAVE_PYTHON - Section "Python ${PYVER}" SecPython - SectionIn 1 2 - - !ifdef REGVIEW - SetRegView ${REGVIEW} - !endif - - SetDetailsPrint both - DetailPrint "Installing Python ${PYVER} (${REGVIEW}-bit)..." - SetDetailsPrint listonly + !ifdef HAVE_GL + Section "OpenGL" SecOpenGL + SectionIn 1 2 3 RO SetOutPath "$INSTDIR\bin" - File /nonfatal "${BUILT}\bin\python*.dll" + File "${BUILT}\bin\libpandagl.dll" + SectionEnd + !endif - SetOutPath "$INSTDIR\python" - File /r /x *.pdb "${BUILT}\python\*" + !ifdef HAVE_DX9 + Section "Direct3D 9" SecDirect3D9 + SectionIn 1 2 - SetDetailsPrint both - DetailPrint "Adding registry keys for Python..." - SetDetailsPrint listonly + SetOutPath "$INSTDIR\bin" + File "${BUILT}\bin\libpandadx9.dll" + File /nonfatal /r "${BUILT}\bin\cgD3D9.dll" + SectionEnd + !endif - ; Check if a copy of Python is installed for this user. - ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" "" - StrCmp "$0" "$INSTDIR\python" RegPath 0 - StrCmp "$0" "" SkipFileCheck 0 - IfFileExists "$0\python.exe" AskRegPath 0 - SkipFileCheck: + !ifdef HAVE_OPENAL + Section "OpenAL Audio" SecOpenAL + SectionIn 1 2 3 - ; Check if a system-wide copy of Python is installed. - ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" "" - StrCmp "$0" "$INSTDIR\python" RegPath 0 - StrCmp "$0" "" RegPath 0 - IfFileExists "$0\python.exe" AskRegPath RegPath + SetOutPath "$INSTDIR\bin" + File "${BUILT}\bin\libp3openal_audio.dll" + File /nonfatal /r "${BUILT}\bin\OpenAL32.dll" + File /nonfatal /r "${BUILT}\bin\*_oal.dll" + SectionEnd + !endif - AskRegPath: - MessageBox MB_YESNO|MB_ICONQUESTION \ - "You already have a copy of Python ${PYVER} installed in:$\r$\n$0$\r$\n$\r$\nPanda3D installs its own copy of Python ${PYVER}, which will install alongside your existing copy. Would you like to make Panda's copy the default Python for your user account?" \ - IDNO SkipRegPath + !ifdef HAVE_FMOD + Section "FMOD Audio" SecFMOD + SectionIn 1 2 - RegPath: - WriteRegStr HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" "" "$INSTDIR\python" - SkipRegPath: + SetOutPath "$INSTDIR\bin" + File "${BUILT}\bin\libp3fmod_audio.dll" + File /r "${BUILT}\bin\fmodex*.dll" + SectionEnd + !endif + !ifdef HAVE_FFMPEG + Section "FFMpeg" SecFFMpeg + SectionIn 1 2 + + SetOutPath "$INSTDIR\bin" + File "${BUILT}\bin\libp3ffmpeg.dll" + File /nonfatal /r "${BUILT}\bin\av*.dll" + File /nonfatal /r "${BUILT}\bin\swscale*.dll" + File /nonfatal /r "${BUILT}\bin\swresample*.dll" + File /nonfatal /r "${BUILT}\bin\postproc*.dll" + SectionEnd + !endif + + !ifdef HAVE_BULLET + Section "Bullet Physics" SecBullet + SectionIn 1 2 + + SetOutPath "$INSTDIR\bin" + File "${BUILT}\bin\libpandabullet.dll" + SectionEnd + !endif + + !ifdef HAVE_ODE + Section "ODE Physics" SecODE + SectionIn 1 2 + + SetOutPath "$INSTDIR\bin" + File "${BUILT}\bin\libpandaode.dll" + SectionEnd + !endif + + !ifdef HAVE_PHYSX + Section "NVIDIA PhysX" SecPhysX + ; Only enable in "Full" + SectionIn 2 + + SetOutPath "$INSTDIR\bin" + File "${BUILT}\bin\libpandaphysx.dll" + File /nonfatal /r "${BUILT}\bin\PhysX*.dll" + File /nonfatal /r "${BUILT}\bin\NxCharacter*.dll" + File /nonfatal /r "${BUILT}\bin\cudart*.dll" + SectionEnd + !endif + + !ifdef HAVE_ROCKET + Section "libRocket GUI" SecRocket + SectionIn 1 2 + + SetOutPath "$INSTDIR\bin" + File "${BUILT}\bin\libp3rocket.dll" + File /nonfatal /r "${BUILT}\bin\Rocket*.dll" + File /nonfatal /r "${BUILT}\bin\_rocket*.pyd" + File /nonfatal /r "${BUILT}\bin\boost_python*.dll" SectionEnd !endif SectionGroupEnd +Section "Tools and utilities" SecTools + SectionIn 1 2 3 + + SetDetailsPrint both + DetailPrint "Installing utilities..." + SetDetailsPrint listonly + + SetOutPath "$INSTDIR\bin" + File /r "${BUILT}\bin\*.exe" + File /nonfatal /r "${BUILT}\bin\*.p3d" + SetOutPath "$INSTDIR\NSIS" + File /r /x CVS "${NSISDIR}\*" + + WriteRegStr HKCU "Software\Classes\Panda3D.Model" "" "Panda3D model/animation" + WriteRegStr HKCU "Software\Classes\Panda3D.Model\DefaultIcon" "" "$INSTDIR\bin\pview.exe" + WriteRegStr HKCU "Software\Classes\Panda3D.Model\shell" "" "open" + WriteRegStr HKCU "Software\Classes\Panda3D.Model\shell\open\command" "" '"$INSTDIR\bin\pview.exe" -l "%1"' + WriteRegStr HKCU "Software\Classes\Panda3D.Model\shell\compress" "" "Compress to .pz" + WriteRegStr HKCU "Software\Classes\Panda3D.Model\shell\compress\command" "" '"$INSTDIR\bin\pzip.exe" "%1"' + + WriteRegStr HKCU "Software\Classes\Panda3D.Compressed" "" "Compressed file" + WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\DefaultIcon" "" "$INSTDIR\bin\pzip.exe" + WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\shell" "" "open" + WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\shell\open\command" "" '"$INSTDIR\bin\pview.exe" -l "%1"' + WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\shell\decompress" "" "Decompress" + WriteRegStr HKCU "Software\Classes\Panda3D.Compressed\shell\decompress\command" "" '"$INSTDIR\bin\punzip.exe" "%1"' + + WriteRegStr HKCU "Software\Classes\Panda3D.Multifile" "" "Panda3D Multifile" + WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\DefaultIcon" "" "$INSTDIR\bin\multify.exe" + WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell" "" "open" + WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract" "" "Extract here" + WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract\command" "" '"$INSTDIR\bin\multify.exe" -xf "%1"' +SectionEnd + +SectionGroup "Python modules" SecGroupPython + Section "Shared code" SecPyShared + SectionIn 1 2 3 + + SetDetailsPrint both + DetailPrint "Installing Panda3D shared Python modules..." + SetDetailsPrint listonly + + SetOutPath $INSTDIR\direct\directscripts + File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\directscripts\*" + SetOutPath $INSTDIR\direct\filter + File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\filter\*.sha" + SetOutPath $INSTDIR\direct + File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\*.py" + + Delete "$INSTDIR\panda3d.py" + Delete "$INSTDIR\panda3d.pyc" + Delete "$INSTDIR\panda3d.pyo" + SetOutPath $INSTDIR\pandac + File /r "${BUILT}\pandac\*.py" + SetOutPath $INSTDIR\panda3d + File /r "${BUILT}\panda3d\*.py" + SectionEnd + + !insertmacro PyBindingSection 2.7 .pyd + !if "${REGVIEW}" == "32" + !insertmacro PyBindingSection 3.5-32 .cp35-win32.pyd + !insertmacro PyBindingSection 3.6-32 .cp36-win32.pyd + !insertmacro PyBindingSection 3.7-32 .cp37-win32.pyd + !insertmacro PyBindingSection 3.8-32 .cp38-win32.pyd + !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd + !else + !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd + !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd + !insertmacro PyBindingSection 3.7 .cp37-win_amd64.pyd + !insertmacro PyBindingSection 3.8 .cp38-win_amd64.pyd + !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd + !endif +SectionGroupEnd + +!ifdef INCLUDE_PYVER +Section "Python ${INCLUDE_PYVER}" SecPython + SectionIn 1 2 3 + + !ifdef REGVIEW + SetRegView ${REGVIEW} + !endif + + SetDetailsPrint both + DetailPrint "Installing Python ${INCLUDE_PYVER} interpreter (${REGVIEW}-bit)..." + SetDetailsPrint listonly + + SetOutPath "$INSTDIR\bin" + File /nonfatal "${BUILT}\bin\python*.dll" + + SetOutPath "$INSTDIR\python" + File /r /x *.pdb "${BUILT}\python\*" + + SetDetailsPrint both + DetailPrint "Adding registry keys for Python..." + SetDetailsPrint listonly + + ; Check if a copy of Python is installed for this user. + ReadRegStr $0 HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "" + StrCmp "$0" "$INSTDIR\python" RegPath 0 + StrCmp "$0" "" SkipFileCheck 0 + IfFileExists "$0\python.exe" AskRegPath 0 + SkipFileCheck: + + ; Check if a system-wide copy of Python is installed. + ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "" + StrCmp "$0" "$INSTDIR\python" RegPath 0 + StrCmp "$0" "" RegPath 0 + IfFileExists "$0\python.exe" AskRegPath RegPath + + AskRegPath: + MessageBox MB_YESNO|MB_ICONQUESTION \ + "You already have a copy of Python ${INCLUDE_PYVER} installed in:$\r$\n$0$\r$\n$\r$\nPanda3D installs its own copy of Python ${INCLUDE_PYVER}, which will install alongside your existing copy. Would you like to make Panda's copy the default Python for your user account?" \ + IDNO SkipRegPath + + RegPath: + WriteRegStr HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "" "$INSTDIR\python" + WriteRegStr HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "ExecutablePath" "$INSTDIR\python\python.exe" + SkipRegPath: + +SectionEnd +!endif + +!macro MaybeEnablePyBindingSection PYVER + !if "${INCLUDE_PYVER}" != "${PYVER}" + !ifdef SecPyBindings${PYVER} + ; Check if a copy of Python is installed for this user. + Push $0 + ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" "" + StrCmp "$0" "" +2 0 + IfFileExists "$0\python.exe" Py${PYVER}Exists 0 + + ; Check if a system-wide copy of Python is installed. + ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" "" + StrCmp "$0" "" Py${PYVER}ExistsNot 0 + IfFileExists "$0\python.exe" Py${PYVER}Exists Py${PYVER}ExistsNot + + Py${PYVER}Exists: + SectionSetFlags ${SecPyBindings${PYVER}} ${SF_SELECTED} + SectionSetInstTypes ${SecPyBindings${PYVER}} 3 + + Py${PYVER}ExistsNot: + Pop $0 + !endif + !endif +!macroend + +Function .onInit + ${If} ${REGVIEW} = 64 + ${AndIfNot} ${RunningX64} + MessageBox MB_OK|MB_ICONEXCLAMATION "You are attempting to install the 64-bit version of Panda3D on a 32-bit version of Windows. Please download and install the 32-bit version of Panda3D instead." + Abort + ${EndIf} + + !ifdef REGVIEW + SetRegView ${REGVIEW} + !endif + + ; We never check for 2.7; it is always enabled in Auto mode + !if "${REGVIEW}" == "32" + !insertmacro MaybeEnablePyBindingSection 3.5-32 + !insertmacro MaybeEnablePyBindingSection 3.6-32 + !insertmacro MaybeEnablePyBindingSection 3.7-32 + !insertmacro MaybeEnablePyBindingSection 3.8-32 + !insertmacro MaybeEnablePyBindingSection 3.9-32 + !else + !insertmacro MaybeEnablePyBindingSection 3.5 + !insertmacro MaybeEnablePyBindingSection 3.6 + !insertmacro MaybeEnablePyBindingSection 3.7 + !insertmacro MaybeEnablePyBindingSection 3.8 + !insertmacro MaybeEnablePyBindingSection 3.9 + !endif +FunctionEnd + +Function .onSelChange + ; If someone selects any Python version, the "shared modules" must be on. + ${If} ${SectionIsPartiallySelected} ${SecGroupPython} + SectionGetFlags ${SecPyShared} $R0 + IntOp $R0 $R0 | ${SF_SELECTED} + SectionSetFlags ${SecPyShared} $R0 + ${EndIf} +FunctionEnd + +!ifdef INCLUDE_PYVER Function ConfirmPythonSelection ; Check the current state of the "Python" section selection. SectionGetFlags ${SecPython} $R0 @@ -474,16 +549,14 @@ Function ConfirmPythonSelection StrCmp $R1 ${SF_SELECTED} SkipCheck 0 ; Maybe the user just doesn't want Python support at all? - SectionGetFlags ${SecPyBindings} $R1 - IntOp $R1 $R1 & ${SF_SELECTED} - StrCmp $R1 ${SF_SELECTED} 0 SkipCheck + !insertmacro SectionFlagIsSet ${SecGroupPython} ${SF_PSELECTED} 0 SkipCheck !ifdef REGVIEW SetRegView ${REGVIEW} !endif ; Check for a user installation of Python. - ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" "" + ReadRegStr $0 HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "" StrCmp $0 "$INSTDIR\python" CheckSystemWidePython 0 StrCmp $0 "" CheckSystemWidePython 0 IfFileExists "$0\ppython.exe" CheckSystemWidePython 0 @@ -491,7 +564,7 @@ Function ConfirmPythonSelection ; Check for a system-wide Python installation. CheckSystemWidePython: - ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" "" + ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "" StrCmp $0 "$INSTDIR\python" AskConfirmation 0 StrCmp $0 "" AskConfirmation 0 IfFileExists "$0\ppython.exe" AskConfirmation 0 @@ -501,7 +574,7 @@ Function ConfirmPythonSelection ; of a different Panda3D build.) Ask the user if he's sure about this. AskConfirmation: MessageBox MB_YESNO|MB_ICONQUESTION \ - "You do not appear to have a ${REGVIEW}-bit version of Python ${PYVER} installed. Are you sure you don't want Panda to install a compatible copy of Python?$\r$\n$\r$\nIf you choose Yes, you will not be able to do Python development with Panda3D until you install a ${REGVIEW}-bit version of Python ${PYVER} and manually configure it to be able to use Panda3D." \ + "You do not appear to have a ${REGVIEW}-bit version of Python ${INCLUDE_PYVER} installed. Are you sure you don't want Panda to install a compatible copy of Python?$\r$\n$\r$\nIf you choose Yes, you will not be able to do Python development with Panda3D until you install a ${REGVIEW}-bit version of Python and install the bindings for this version." \ IDYES SkipCheck ; User clicked no, so re-enable the select box and abort. @@ -511,9 +584,10 @@ Function ConfirmPythonSelection SkipCheck: FunctionEnd +!endif Section "C++ support" SecHeadersLibs - SectionIn 1 + SectionIn 1 2 SetDetailsPrint both DetailPrint "Installing header files..." @@ -532,7 +606,7 @@ SectionEnd !ifdef HAVE_SAMPLES Section "Sample programs" SecSamples - SectionIn 1 + SectionIn 1 2 ; Necessary for proper start menu shortcut installation SetShellVarContext current @@ -599,7 +673,7 @@ SectionEnd !ifdef HAVE_MAX_PLUGINS Section "3ds Max plug-ins" SecMaxPlugins - SectionIn 1 3 + SectionIn 1 2 SetDetailsPrint both DetailPrint "Installing Autodesk 3ds Max plug-ins..." @@ -614,7 +688,7 @@ SectionEnd !ifdef HAVE_MAYA_PLUGINS Section "Maya plug-ins" SecMayaPlugins - SectionIn 1 3 + SectionIn 1 2 SetDetailsPrint both DetailPrint "Installing Autodesk Maya plug-ins..." @@ -737,17 +811,30 @@ Section Uninstall DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\DefaultIcon" DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\shell" - ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" "" - StrCmp $0 "$INSTDIR\python" 0 +2 - DeleteRegKey HKLM "Software\Python\PythonCore\${PYVER}" + !ifdef INCLUDE_PYVER + ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "" + StrCmp $0 "$INSTDIR\python" 0 +2 + DeleteRegKey HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}" - ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" "" - StrCmp $0 "$INSTDIR\python" 0 +2 - DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}" + ReadRegStr $0 HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "" + StrCmp $0 "$INSTDIR\python" 0 +2 + DeleteRegKey HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}" + !endif - ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" "" - StrCmp $0 "$INSTDIR" 0 +2 - DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" + !insertmacro RemovePythonPath 2.7 + !if "${REGVIEW}" == "32" + !insertmacro RemovePythonPath 3.5-32 + !insertmacro RemovePythonPath 3.6-32 + !insertmacro RemovePythonPath 3.7-32 + !insertmacro RemovePythonPath 3.8-32 + !insertmacro RemovePythonPath 3.9-32 + !else + !insertmacro RemovePythonPath 3.5 + !insertmacro RemovePythonPath 3.6 + !insertmacro RemovePythonPath 3.7 + !insertmacro RemovePythonPath 3.8 + !insertmacro RemovePythonPath 3.9 + !endif SetDetailsPrint both DetailPrint "Deleting files..." @@ -812,8 +899,23 @@ SectionEnd !insertmacro MUI_DESCRIPTION_TEXT ${SecRocket} $(DESC_SecRocket) !endif !insertmacro MUI_DESCRIPTION_TEXT ${SecTools} $(DESC_SecTools) - !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings} $(DESC_SecPyBindings) - !ifdef HAVE_PYTHON + !insertmacro MUI_DESCRIPTION_TEXT ${SecGroupPython} $(DESC_SecGroupPython) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyShared} $(DESC_SecPyShared) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings2.7} $(DESC_SecPyBindings2.7) + !if "${REGVIEW}" == "32" + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5-32} $(DESC_SecPyBindings3.5-32) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6-32} $(DESC_SecPyBindings3.6-32) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7-32} $(DESC_SecPyBindings3.7-32) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8-32} $(DESC_SecPyBindings3.8-32) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32) + !else + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7} $(DESC_SecPyBindings3.7) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8} $(DESC_SecPyBindings3.8) + !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9) + !endif + !ifdef INCLUDE_PYVER !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython) !endif !insertmacro MUI_DESCRIPTION_TEXT ${SecHeadersLibs} $(DESC_SecHeadersLibs) diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index ce7282626e..e23bedffd7 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -168,17 +168,10 @@ def MakeInstallerNSIS(version, file, title, installdir, runtime=False, compresso elif os.path.isdir(file): shutil.rmtree(file) - if "PYTHONVERSION" in SDK: - pyver = SDK["PYTHONVERSION"][6:9] - else: - pyver = "%d.%d" % (sys.version_info[:2]) - if GetTargetArch() == 'x64': regview = '64' else: regview = '32' - if int(pyver[0]) == 3 and int(pyver[2]) >= 5: - pyver += '-32' if runtime: # Invoke the make_installer script. @@ -211,11 +204,22 @@ def MakeInstallerNSIS(version, file, title, installdir, runtime=False, compresso 'OUTFILE' : '..\\' + file, 'BUILT' : '..\\' + outputdir, 'SOURCE' : '..', - 'PYVER' : pyver, 'REGVIEW' : regview, - 'EXT_SUFFIX': GetExtensionSuffix(), } + # Are we shipping a version of Python? + if os.path.isfile(os.path.join(outputdir, "python", "python.exe")): + py_dlls = glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9].dll")) \ + + glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9]_d.dll")) + assert py_dlls + py_dll = os.path.basename(py_dlls[0]) + pyver = py_dll[6] + "." + py_dll[7] + + if GetTargetArch() != 'x64': + pyver += '-32' + + nsis_defs['INCLUDE_PYVER'] = pyver + if GetHost() == 'windows': cmd = os.path.join(GetThirdpartyBase(), 'win-nsis', 'makensis') + ' /V2' for item in nsis_defs.items(): @@ -651,7 +655,7 @@ def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs): dist.write(' \n') if python_versions: dist.write(' \n') - for version_info in python_versions: + for version_info in sorted(python_versions, key=lambda info:info["version"], reverse=True): dist.write(' \n' % (version_info["version"])) dist.write(' \n') dist.write(' \n') @@ -978,13 +982,9 @@ def MakeInstaller(version, **kwargs): fn += version - if "PYTHONVERSION" in SDK: - pyver = SDK["PYTHONVERSION"][6:9] - else: - pyver = "%d.%d" % (sys.version_info[:2]) - - if not runtime and pyver != "2.7": - fn += '-py' + pyver + python_versions = kwargs.get('python_versions', []) + if not runtime and len(python_versions) == 1 and python_versions[0]["version"] != "2.7": + fn += '-py' + python_versions[0]["version"] if GetOptimize() <= 2: fn += "-dbg" From f29643dd14bd2cbf52037216b0cbd75e4b94c7b4 Mon Sep 17 00:00:00 2001 From: Donny Lawrence Date: Wed, 26 Dec 2018 22:46:29 +0100 Subject: [PATCH 377/424] cocoa: Prevent a deadlock when using SIMPLE_THREADS on macOS Closes #491 Fixes #490 --- panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h | 4 ++-- .../src/cocoadisplay/cocoaGraphicsStateGuardian.mm | 6 ++++-- panda/src/cocoadisplay/cocoaGraphicsWindow.mm | 3 ++- panda/src/pipeline/conditionVarImpl.h | 13 +++++++++++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h index 8365834a16..c8820a73d3 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h @@ -50,8 +50,8 @@ public: FrameBufferProperties _fbprops; CVDisplayLinkRef _display_link = nullptr; - Mutex _swap_lock; - ConditionVar _swap_condition; + TrueMutexImpl _swap_lock; + TrueConditionVarImpl _swap_condition; AtomicAdjust::Integer _last_wait_frame = 0; protected: diff --git a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm index 92f6226492..aac8beb033 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm @@ -37,8 +37,9 @@ display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now, const CVTimeStamp* output_time, CVOptionFlags flags_in, CVOptionFlags *flags_out, void *context) { CocoaGraphicsStateGuardian *gsg = (CocoaGraphicsStateGuardian *)context; - MutexHolder swap_holder(gsg->_swap_lock); + gsg->_swap_lock.lock(); gsg->_swap_condition.notify(); + gsg->_swap_lock.unlock(); return kCVReturnSuccess; } @@ -73,8 +74,9 @@ CocoaGraphicsStateGuardian:: if (_display_link != nil) { CVDisplayLinkRelease(_display_link); _display_link = nil; - MutexHolder swap_holder(_swap_lock); + _swap_lock.lock(); _swap_condition.notify(); + _swap_lock.unlock(); } if (_context != nil) { [_context clearDrawable]; diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index 85e01917eb..c3f23d4013 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm @@ -277,8 +277,9 @@ end_flip() { if (_vsync_enabled) { AtomicAdjust::Integer cur_frame = ClockObject::get_global_clock()->get_frame_count(); if (AtomicAdjust::set(cocoagsg->_last_wait_frame, cur_frame) != cur_frame) { - MutexHolder swap_holder(cocoagsg->_swap_lock); + cocoagsg->_swap_lock.lock(); cocoagsg->_swap_condition.wait(); + cocoagsg->_swap_lock.unlock(); } } diff --git a/panda/src/pipeline/conditionVarImpl.h b/panda/src/pipeline/conditionVarImpl.h index 499a5b0c62..7837622ba6 100644 --- a/panda/src/pipeline/conditionVarImpl.h +++ b/panda/src/pipeline/conditionVarImpl.h @@ -50,4 +50,17 @@ typedef ConditionVarPosixImpl ConditionVarFullImpl; #endif +#if defined(WIN32_VC) +#include "conditionVarWin32Impl.h" +typedef ConditionVarWin32Impl TrueConditionVarImpl; + +#elif defined(HAVE_POSIX_THREADS) +#include "conditionVarPosixImpl.h" +typedef ConditionVarPosixImpl TrueConditionVarImpl; + +#else +// No true threads, sorry. Better not try to use 'em. + +#endif + #endif From a9d6d3be46be68576008577f1ca1c453b23cddd7 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 26 Dec 2018 20:03:14 +0100 Subject: [PATCH 378/424] interrogate: refactor default argument handling --- dtool/src/cppparser/cppExpression.cxx | 60 +++- dtool/src/cppparser/cppExpression.h | 1 + .../interfaceMakerPythonNative.cxx | 322 +++++++++--------- 3 files changed, 220 insertions(+), 163 deletions(-) diff --git a/dtool/src/cppparser/cppExpression.cxx b/dtool/src/cppparser/cppExpression.cxx index 2b2cc0bd50..78da68b5a7 100644 --- a/dtool/src/cppparser/cppExpression.cxx +++ b/dtool/src/cppparser/cppExpression.cxx @@ -25,6 +25,7 @@ #include "cppFunctionGroup.h" #include "cppFunctionType.h" #include "cppClosureType.h" +#include "cppReferenceType.h" #include "cppStructType.h" #include "cppBison.h" #include "pdtoa.h" @@ -260,12 +261,12 @@ CPPExpression(CPPIdentifier *ident, CPPScope *current_scope, _u._variable = inst; return; } - CPPFunctionGroup *fgroup = decl->as_function_group(); + /*CPPFunctionGroup *fgroup = decl->as_function_group(); if (fgroup != nullptr) { _type = T_function; _u._fgroup = fgroup; return; - } + }*/ } _type = T_unknown_ident; @@ -1232,6 +1233,61 @@ determine_type() const { return nullptr; // Compiler kludge; can't get here. } +/** + * Returns true if this is an lvalue expression. + */ +bool CPPExpression:: +is_lvalue() const { + switch (_type) { + case T_variable: + case T_function: + case T_unknown_ident: + return true; + + case T_typecast: + case T_static_cast: + case T_dynamic_cast: + case T_const_cast: + case T_reinterpret_cast: + { + CPPReferenceType *ref_type = _u._typecast._to->as_reference_type(); + return ref_type != nullptr && ref_type->_value_category == CPPReferenceType::VC_lvalue; + } + + case T_unary_operation: + if (_u._op._operator == 'f') { + // A function returning an lvalue reference. + CPPType *return_type = determine_type(); + if (return_type != nullptr) { + CPPReferenceType *ref_type = return_type->as_reference_type(); + return ref_type != nullptr && ref_type->_value_category == CPPReferenceType::VC_lvalue; + } + } + return _u._op._operator == PLUSPLUS + || _u._op._operator == MINUSMINUS + || _u._op._operator == '*'; + + case T_binary_operation: + if (_u._op._operator == ',') { + CPPReferenceType *ref_type = _u._op._op2->as_reference_type(); + return ref_type != nullptr && ref_type->_value_category == CPPReferenceType::VC_lvalue; + } + return (_u._op._operator == POINTSAT || _u._op._operator == ','); + + case T_trinary_operation: + return _u._op._op2->is_lvalue() && _u._op._op3->is_lvalue(); + + case T_literal: + case T_raw_literal: + return true; + + default: + break; + } + + return false; +} + /** * Returns true if this declaration is an actual, factual declaration, or * false if some part of the declaration depends on a template parameter which diff --git a/dtool/src/cppparser/cppExpression.h b/dtool/src/cppparser/cppExpression.h index 4482727f53..3cea871679 100644 --- a/dtool/src/cppparser/cppExpression.h +++ b/dtool/src/cppparser/cppExpression.h @@ -133,6 +133,7 @@ public: Result evaluate() const; CPPType *determine_type() const; + bool is_lvalue() const; bool is_tbd() const; virtual bool is_fully_specified() const; diff --git a/dtool/src/interrogate/interfaceMakerPythonNative.cxx b/dtool/src/interrogate/interfaceMakerPythonNative.cxx index 59575668d4..3964cad919 100644 --- a/dtool/src/interrogate/interfaceMakerPythonNative.cxx +++ b/dtool/src/interrogate/interfaceMakerPythonNative.cxx @@ -4078,13 +4078,9 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) { /** * Special case optimization: if the last map is a subset of the map before - * it, and the last parameter is only a simple parameter type (that we have - * special default argument handling for), we can merge the cases. When this - * happens, we can make use of a special feature of PyArg_ParseTuple for - * handling of these last few default arguments. This doesn't work well for - * all types of default expressions, though, hence the need for this elaborate - * checking mechanism down here, which goes in parallel with the actual - * optional arg handling logic in write_function_instance. + * it, we can merge the cases. When this happens, we can make use of a + * special feature of PyArg_ParseTuple for handling of these last few default + * arguments. * * This isn't just to help reduce the amount of generated code; it also * enables arbitrary selection of keyword arguments for many functions, ie. @@ -4095,14 +4091,6 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) { * Thanks to this mechanism, we can call it like so: * * func(c=True, d=".") - * - * The return value is the minimum of the number of maximum arguments. - * - * Sorry, let me try that again: it returns the largest number of arguments - * for which the overloads will be separated out rather than handled via the - * special default handling mechanism. Or something. - * - * Please don't hate me. */ int InterfaceMakerPythonNative:: collapse_default_remaps(std::map > &map_sets, @@ -4118,70 +4106,8 @@ collapse_default_remaps(std::map > &map_sets, if (std::includes(rmi_next->second.begin(), rmi_next->second.end(), rmi->second.begin(), rmi->second.end())) { - // Check if the nth argument is something we can easily create a default - // for. - std::set::iterator sii; - for (sii = rmi->second.begin(); sii != rmi->second.end(); ++sii) { - FunctionRemap *remap = (*sii); - size_t pn = (size_t)rmi->first; - if (!remap->_has_this || remap->_type == FunctionRemap::T_constructor) { - --pn; - } - nassertd(pn < remap->_parameters.size()) goto abort_iteration; - - ParameterRemap *param = remap->_parameters[pn]._remap; - CPPType *type = param->get_new_type(); - - if (param->new_type_is_atomic_string()) { - CPPType *orig_type = param->get_orig_type(); - if (TypeManager::is_char_pointer(orig_type)) { - } else if (TypeManager::is_wchar_pointer(orig_type)) { - goto abort_iteration; - } else if (TypeManager::is_wstring(orig_type)) { - goto abort_iteration; - } else if (TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) { - goto abort_iteration; - } else { - // Regular strings are OK if the default argument is a string - // literal or the default string constructor, since those are - // trivial to handle. This actually covers almost all of the - // cases of default string args. - CPPExpression::Type expr_type = param->get_default_value()->_type; - if (expr_type != CPPExpression::T_default_construct && - expr_type != CPPExpression::T_string) { - goto abort_iteration; - } - } - } else if (TypeManager::is_integer(type)) { - } else if (TypeManager::is_float(type)) { - } else if (TypeManager::is_const_char_pointer(type)) { - } else if (TypeManager::is_pointer_to_PyTypeObject(type)) { - } else if (TypeManager::is_pointer_to_PyStringObject(type)) { - } else if (TypeManager::is_pointer_to_PyUnicodeObject(type)) { - } else if (TypeManager::is_pointer_to_PyObject(type)) { - } else if (TypeManager::is_pointer_to_Py_buffer(type)) { - goto abort_iteration; - } else if (TypeManager::is_pointer_to_simple(type)) { - goto abort_iteration; - } else if (TypeManager::is_pointer(type)) { - // I'm allowing other pointer types, but only if the expression - // happens to evaluate to a numeric constant (which will likely only - // be NULL). There are too many issues to resolve right now with - // allowing more complex default expressions, including issues in - // the C++ parser (but the reader is welcome to give it a try!) - CPPExpression::Result res = param->get_default_value()->evaluate(); - if (res._type != CPPExpression::RT_integer && - res._type != CPPExpression::RT_pointer) { - goto abort_iteration; - } - } else { - goto abort_iteration; - } - } - // rmi_next has a superset of the remaps in rmi, and we are going to - // erase rmi_next, so put all the remaps in rmi. rmi->second = - // rmi_next->second; + // erase rmi_next, so put all the remaps in rmi. max_required_args = rmi_next->first; rmi = rmi_next; @@ -4191,7 +4117,6 @@ collapse_default_remaps(std::map > &map_sets, } } -abort_iteration: // Now erase the other remap sets. Reverse iterators are weird, we first // need to get forward iterators and decrement them by one. std::map >::iterator erase_begin, erase_end; @@ -4738,6 +4663,7 @@ write_function_instance(ostream &out, FunctionRemap *remap, "(" + orig_type->get_local_name(&parser) + ")" + param_name; string default_expr; + const char *null_assign = ""; if (is_optional) { // If this is an optional argument, PyArg_ParseTuple will leave the @@ -4747,6 +4673,7 @@ write_function_instance(ostream &out, FunctionRemap *remap, default_expr_str << " = "; default_value->output(default_expr_str, 0, &parser, false); default_expr = default_expr_str.str(); + null_assign = " = nullptr"; // We should only ever have to consider optional arguments for functions // taking a variable number of arguments. @@ -4776,23 +4703,40 @@ write_function_instance(ostream &out, FunctionRemap *remap, expected_params += "str"; } else if (TypeManager::is_wchar_pointer(orig_type)) { - indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n"; - indent(out, indent_level) << "PyObject *" << param_name << ";\n"; - indent(out, indent_level) << "#else\n"; - indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n"; - indent(out, indent_level) << "#endif\n"; + out << "#if PY_VERSION_HEX >= 0x03020000\n"; + indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n"; + out << "#else\n"; + indent(out, indent_level) << "PyUnicodeObject *" << param_name << null_assign << ";\n"; + out << "#endif\n"; format_specifiers += "U"; parameter_list += ", &" + param_name; - extra_convert - << "#if PY_VERSION_HEX >= 0x03030000\n" - << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n" - << "#else" - << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n" - << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n" - << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n" - << param_name << "_str[" << param_name << "_len] = 0;\n" - << "#endif\n"; + if (is_optional) { + extra_convert + << "wchar_t *" << param_name << "_str;\n" + << "if (" << param_name << " != nullptr) {\n" + << "#if PY_VERSION_HEX >= 0x03030000\n" + << " " << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n" + << "#else" + << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n" + << " " << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n" + << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n" + << param_name << "_str[" << param_name << "_len] = 0;\n" + << "#endif\n" + << "} else {\n" + << " " << param_name << "_str" << default_expr << ";\n" + << "}\n"; + } else { + extra_convert + << "#if PY_VERSION_HEX >= 0x03030000\n" + << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n" + << "#else" + << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n" + << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n" + << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n" + << param_name << "_str[" << param_name << "_len] = 0;\n" + << "#endif\n"; + } pexpr_string = param_name + "_str"; @@ -4803,56 +4747,48 @@ write_function_instance(ostream &out, FunctionRemap *remap, expected_params += "unicode"; - } else if (TypeManager::is_wstring(orig_type)) { - indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n"; - indent(out, indent_level) << "PyObject *" << param_name << ";\n"; - indent(out, indent_level) << "#else\n"; - indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n"; - indent(out, indent_level) << "#endif\n"; + } else if (TypeManager::is_wstring(orig_type) || + TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) { + out << "#if PY_VERSION_HEX >= 0x03020000\n"; + indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n"; + out << "#else\n"; + indent(out, indent_level) << "PyUnicodeObject *" << param_name << null_assign << ";\n"; + out << "#endif\n"; format_specifiers += "U"; parameter_list += ", &" + param_name; - extra_convert - << "#if PY_VERSION_HEX >= 0x03030000\n" - << "Py_ssize_t " << param_name << "_len;\n" - << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" - << param_name << ", &" << param_name << "_len);\n" - << "#else\n" - << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n" - << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n" - << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n" - << "#endif\n"; - - pexpr_string = param_name + "_str, " + param_name + "_len"; - - extra_cleanup - << "#if PY_VERSION_HEX >= 0x03030000\n" - << "PyMem_Free(" << param_name << "_str);\n" - << "#endif\n"; - - expected_params += "unicode"; - - } else if (TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) { - indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n"; - indent(out, indent_level) << "PyObject *" << param_name << ";\n"; - indent(out, indent_level) << "#else\n"; - indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n"; - indent(out, indent_level) << "#endif\n"; - format_specifiers += "U"; - parameter_list += ", &" + param_name; - - extra_convert - << "#if PY_VERSION_HEX >= 0x03030000\n" - << "Py_ssize_t " << param_name << "_len;\n" - << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" - << param_name << ", &" << param_name << "_len);\n" - << "#else\n" - << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n" - << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n" - << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n" - << "#endif\n"; - - pexpr_string = param_name + "_str, " + param_name + "_len"; + if (is_optional) { + extra_convert + << "Py_ssize_t " << param_name << "_len;\n" + << "wchar_t *" << param_name << "_str;\n" + << "std::wstring " << param_name << "_wstr;\n" + << "if (" << param_name << " != nullptr) {\n" + << "#if PY_VERSION_HEX >= 0x03030000\n" + << " " << param_name << "_str = PyUnicode_AsWideCharString(" + << param_name << ", &" << param_name << "_len);\n" + << "#else\n" + << " " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n" + << " " << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n" + << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n" + << "#endif\n" + << " " << param_name << "_wstr.assign(" << param_name << "_str, " << param_name << "_len);\n" + << "} else {\n" + << " " << param_name << "_wstr" << default_expr << ";\n" + << "}\n"; + pexpr_string = "std::move(" + param_name + "_wstr)"; + } else { + extra_convert + << "#if PY_VERSION_HEX >= 0x03030000\n" + << "Py_ssize_t " << param_name << "_len;\n" + << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" + << param_name << ", &" << param_name << "_len);\n" + << "#else\n" + << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n" + << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n" + << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n" + << "#endif\n"; + pexpr_string = param_name + "_str, " + param_name + "_len"; + } extra_cleanup << "#if PY_VERSION_HEX >= 0x03030000\n" @@ -5019,11 +4955,11 @@ write_function_instance(ostream &out, FunctionRemap *remap, only_pyobjects = false; } else if (TypeManager::is_wchar(type)) { - indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n"; + out << "#if PY_VERSION_HEX >= 0x03020000\n"; indent(out, indent_level) << "PyObject *" << param_name << ";\n"; - indent(out, indent_level) << "#else\n"; + out << "#else\n"; indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n"; - indent(out, indent_level) << "#endif\n"; + out << "#endif\n"; format_specifiers += "U"; parameter_list += ", &" + param_name; @@ -5348,17 +5284,36 @@ write_function_instance(ostream &out, FunctionRemap *remap, if (args_type == AT_single_arg) { param_name = "arg"; } else { - indent(out, indent_level) << "PyObject *" << param_name << ";\n"; + indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n"; format_specifiers += "O"; parameter_list += ", &" + param_name; } indent(out, indent_level) << "Py_buffer " << param_name << "_view;\n"; - extra_param_check << " && PyObject_GetBuffer(" - << param_name << ", &" - << param_name << "_view, PyBUF_FULL) == 0"; - pexpr_string = "&" + param_name + "_view"; - extra_cleanup << "PyBuffer_Release(&" << param_name << "_view);\n"; + if (is_optional) { + indent(out, indent_level) << "Py_buffer *" << param_name << "_viewp;\n"; + + extra_convert + << "bool " << param_name << "_success;\n" + << "if (" << param_name << " != nullptr) {\n" + << " " << param_name << "_success = (PyObject_GetBuffer(" + << param_name << ", &" << param_name << "_view, PyBUF_FULL) == 0);\n" + << " " << param_name << "_viewp = &" << param_name << "_view;\n" + << "} else {\n" + << " " << param_name << "_viewp" << default_expr << ";\n" + << " " << param_name << "_success = true;\n" + << "}\n"; + + extra_param_check << " && " << param_name << "_success"; + pexpr_string = param_name + "_viewp"; + extra_cleanup << "if (" << param_name << " != nullptr) PyBuffer_Release(&" << param_name << "_view);\n"; + } else { + extra_param_check << " && PyObject_GetBuffer(" + << param_name << ", &" + << param_name << "_view, PyBUF_FULL) == 0"; + pexpr_string = "&" + param_name + "_view"; + extra_cleanup << "PyBuffer_Release(&" << param_name << "_view);\n"; + } expected_params += "buffer"; may_raise_typeerror = true; clear_error = true; @@ -5367,7 +5322,7 @@ write_function_instance(ostream &out, FunctionRemap *remap, if (args_type == AT_single_arg) { param_name = "arg"; } else { - indent(out, indent_level) << "PyObject *" << param_name << ";\n"; + indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n"; format_specifiers += "O"; parameter_list += ", &" + param_name; } @@ -5496,18 +5451,15 @@ write_function_instance(ostream &out, FunctionRemap *remap, if (args_type == AT_single_arg) { param_name = "arg"; } else { - indent(out, indent_level) << "PyObject *" << param_name; - if (is_optional) { - out << " = nullptr"; - } - out << ";\n"; + indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n"; format_specifiers += "O"; parameter_list += ", &" + param_name; } // If the default value is NULL, we also accept a None value. bool maybe_none = false; - if (default_value != nullptr && (return_flags & RF_coerced) == 0) { + if (default_value != nullptr && (return_flags & RF_coerced) == 0 && + TypeManager::is_pointer(orig_type)) { CPPExpression::Result res = param->get_default_value()->evaluate(); if (res._type == CPPExpression::RT_integer || res._type == CPPExpression::RT_pointer) { @@ -5576,8 +5528,11 @@ write_function_instance(ostream &out, FunctionRemap *remap, << "if (" << param_name << " != nullptr && " << param_name << " != Py_None) {\n" << " " << param_name << "_this"; } else if (is_optional) { + if (TypeManager::is_pointer(orig_type)) { + extra_convert << default_expr; + } extra_convert - << default_expr << ";\n" + << ";\n" << "if (" << param_name << " != nullptr) {\n" << " " << param_name << "_this"; } else if (maybe_none) { @@ -5590,7 +5545,13 @@ write_function_instance(ostream &out, FunctionRemap *remap, extra_convert << " = Dtool_Coerce_" + make_safe_name(class_name) + "(" + param_name + ", " + param_name + "_local);\n"; - if (is_optional || maybe_none) { + if (is_optional && !TypeManager::is_pointer(orig_type)) { + extra_convert + << "} else {\n" + << " " << param_name << "_local" << default_expr << ";\n" + << " " << param_name << "_this = &" << param_name << "_local;\n" + << "}\n"; + } else if (is_optional || maybe_none) { extra_convert << "}\n"; } @@ -5645,21 +5606,60 @@ write_function_instance(ostream &out, FunctionRemap *remap, } else { // The regular, non-coercion case. type->output_instance(extra_convert, param_name + "_this", &parser); if (is_optional && maybe_none) { + // This parameter has a default value of nullptr, so we need to also + // allow passing in None. extra_convert << default_expr << ";\n" << "if (" << param_name << " != nullptr && " << param_name << " != Py_None) {\n" << " " << param_name << "_this"; - } else if (is_optional) { + } + else if (is_optional && !TypeManager::is_pointer(orig_type) && !default_value->is_lvalue()) { + // Most annoying case, where we have to use an rvalue reference to + // extend the lifetime of the default argument. In this case, the + // default expression is invoked even if not used. + extra_convert << ";\n"; + if (TypeManager::is_const_pointer_to_anything(type)) { + extra_convert << "const "; + obj_type->output_instance(extra_convert, "&" + param_name + "_ref", &parser); + } else { + obj_type->output_instance(extra_convert, "&&" + param_name + "_ref", &parser); + } extra_convert << default_expr << ";\n" - << "if (" << param_name << " != nullptr) {\n" + << "if (" << param_name << " == nullptr) {\n" + << " " << param_name << "_this = &" << param_name << "_ref;\n" + << "} else {\n" << " " << param_name << "_this"; - } else if (maybe_none) { + } + else if (is_optional) { + // General case where the default argument is either an lvalue or a + // pointer. + extra_convert + << ";\n" + << "if (" << param_name << " == nullptr) {\n" + << " " << param_name << "_this = "; + if (TypeManager::is_pointer(orig_type)) { + default_value->output(extra_convert, 0, &parser, false); + extra_convert << ";\n"; + } else { + // The rvalue case was handled above, so this is an lvalue, which + // means we can safely take a reference to it. + extra_convert << "&("; + default_value->output(extra_convert, 0, &parser, false); + extra_convert << ");\n"; + } + extra_convert + << "} else {\n" + << " " << param_name << "_this"; + } + else if (maybe_none) { + // No default argument, but we still need to check for None. extra_convert << " = nullptr;\n" << "if (" << param_name << " != Py_None) {\n" << " " << param_name << "_this"; } + if (const_ok && !report_errors) { // This function does the same thing in this case and is slightly // simpler. But maybe we should just reorganize these functions From 669a65536623ac8ba0e3f5c3186c23cf3c0ce5de Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 26 Dec 2018 22:44:26 +0100 Subject: [PATCH 379/424] Fix ShaderBuffer cleanup assertion with state cache disabled --- panda/src/glstuff/glBufferContext_src.I | 5 ++- panda/src/glstuff/glBufferContext_src.h | 3 +- panda/src/glstuff/glGraphicsBuffer_src.cxx | 2 +- .../glstuff/glGraphicsStateGuardian_src.cxx | 2 +- panda/src/gobj/bufferContext.I | 8 ++++ panda/src/gobj/bufferContext.cxx | 3 +- panda/src/gobj/bufferContext.h | 12 +++++- panda/src/gobj/indexBufferContext.I | 15 +++---- panda/src/gobj/indexBufferContext.h | 3 -- panda/src/gobj/preparedGraphicsObjects.cxx | 43 +++++++++++-------- panda/src/gobj/shaderBuffer.cxx | 25 +++++++++++ panda/src/gobj/shaderBuffer.h | 5 +++ panda/src/gobj/textureContext.I | 19 ++++---- panda/src/gobj/textureContext.h | 3 -- panda/src/gobj/vertexBufferContext.I | 15 +++---- panda/src/gobj/vertexBufferContext.h | 4 -- 16 files changed, 106 insertions(+), 61 deletions(-) diff --git a/panda/src/glstuff/glBufferContext_src.I b/panda/src/glstuff/glBufferContext_src.I index bec8283a18..56ecf2102a 100644 --- a/panda/src/glstuff/glBufferContext_src.I +++ b/panda/src/glstuff/glBufferContext_src.I @@ -16,8 +16,9 @@ */ INLINE CLP(BufferContext):: CLP(BufferContext)(CLP(GraphicsStateGuardian) *glgsg, - PreparedGraphicsObjects *pgo) : - BufferContext(&pgo->_sbuffer_residency), + PreparedGraphicsObjects *pgo, + TypedWritableReferenceCount *object) : + BufferContext(&pgo->_sbuffer_residency, object), AdaptiveLruPage(0), _glgsg(glgsg) { diff --git a/panda/src/glstuff/glBufferContext_src.h b/panda/src/glstuff/glBufferContext_src.h index b6c46aab76..b67a9d214e 100644 --- a/panda/src/glstuff/glBufferContext_src.h +++ b/panda/src/glstuff/glBufferContext_src.h @@ -21,7 +21,8 @@ class EXPCL_GL CLP(BufferContext) : public BufferContext, public AdaptiveLruPage { public: INLINE CLP(BufferContext)(CLP(GraphicsStateGuardian) *glgsg, - PreparedGraphicsObjects *pgo); + PreparedGraphicsObjects *pgo, + TypedWritableReferenceCount *object); ALLOC_DELETED_CHAIN(CLP(BufferContext)); virtual void evict_lru(); diff --git a/panda/src/glstuff/glGraphicsBuffer_src.cxx b/panda/src/glstuff/glGraphicsBuffer_src.cxx index 8fbba8f79d..ebb038de7a 100644 --- a/panda/src/glstuff/glGraphicsBuffer_src.cxx +++ b/panda/src/glstuff/glGraphicsBuffer_src.cxx @@ -1413,7 +1413,7 @@ open_buffer() { } if (_rb_context == nullptr) { - _rb_context = new BufferContext(&(glgsg->_renderbuffer_residency)); + _rb_context = new BufferContext(&(glgsg->_renderbuffer_residency), nullptr); } /* diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index df2095284c..1e6d1781cc 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -6519,7 +6519,7 @@ prepare_shader_buffer(ShaderBuffer *data) { if (_supports_shader_buffers) { PStatGPUTimer timer(this, _prepare_shader_buffer_pcollector); - CLP(BufferContext) *gbc = new CLP(BufferContext)(this, _prepared_objects); + CLP(BufferContext) *gbc = new CLP(BufferContext)(this, _prepared_objects, data); _glGenBuffers(1, &gbc->_index); if (GLCAT.is_debug() && gl_debug_buffers) { diff --git a/panda/src/gobj/bufferContext.I b/panda/src/gobj/bufferContext.I index 3c34d93a8a..6d564cef02 100644 --- a/panda/src/gobj/bufferContext.I +++ b/panda/src/gobj/bufferContext.I @@ -11,6 +11,14 @@ * @date 2006-03-16 */ +/** + * Returns the associated object. + */ +INLINE TypedWritableReferenceCount *BufferContext:: +get_object() const { + return _object; +} + /** * Returns the number of bytes previously reported for the data object. This * is used to track changes in the data object's allocated size; if it changes diff --git a/panda/src/gobj/bufferContext.cxx b/panda/src/gobj/bufferContext.cxx index 52cac06ce4..b268400d35 100644 --- a/panda/src/gobj/bufferContext.cxx +++ b/panda/src/gobj/bufferContext.cxx @@ -19,7 +19,8 @@ TypeHandle BufferContext::_type_handle; * */ BufferContext:: -BufferContext(BufferResidencyTracker *residency) : +BufferContext(BufferResidencyTracker *residency, TypedWritableReferenceCount *object) : + _object(object), _residency(residency), _residency_state(0), _data_size_bytes(0), diff --git a/panda/src/gobj/bufferContext.h b/panda/src/gobj/bufferContext.h index dedf5411e9..60c9ab3333 100644 --- a/panda/src/gobj/bufferContext.h +++ b/panda/src/gobj/bufferContext.h @@ -23,6 +23,7 @@ #include "bufferResidencyTracker.h" class PreparedGraphicsObjects; +class TypedWritableReferenceCount; /** * This is a base class for those kinds of SavedContexts that occupy an @@ -36,15 +37,19 @@ class PreparedGraphicsObjects; */ class EXPCL_PANDA_GOBJ BufferContext : public SavedContext, private LinkedListNode { public: - BufferContext(BufferResidencyTracker *residency); + BufferContext(BufferResidencyTracker *residency, TypedWritableReferenceCount *object); virtual ~BufferContext(); + INLINE TypedWritableReferenceCount *get_object() const; + PUBLISHED: INLINE size_t get_data_size_bytes() const; INLINE UpdateSeq get_modified() const; INLINE bool get_active() const; INLINE bool get_resident() const; + MAKE_PROPERTY(object, get_object); + MAKE_PROPERTY(data_size_bytes, get_data_size_bytes); MAKE_PROPERTY(modified, get_modified); MAKE_PROPERTY(active, get_active); @@ -62,6 +67,11 @@ public: private: void set_owning_chain(BufferContextChain *chain); +protected: + // This cannot be a PT(), because the object and the GSG both own their + // BufferContexts! That would create a circular reference count. + TypedWritableReferenceCount *_object; + private: BufferResidencyTracker *_residency; int _residency_state; diff --git a/panda/src/gobj/indexBufferContext.I b/panda/src/gobj/indexBufferContext.I index caf5e7f34a..e55304bfcb 100644 --- a/panda/src/gobj/indexBufferContext.I +++ b/panda/src/gobj/indexBufferContext.I @@ -16,9 +16,8 @@ */ INLINE IndexBufferContext:: IndexBufferContext(PreparedGraphicsObjects *pgo, GeomPrimitive *data) : - BufferContext(&pgo->_ibuffer_residency), - AdaptiveLruPage(0), - _data(data) + BufferContext(&pgo->_ibuffer_residency, data), + AdaptiveLruPage(0) { } @@ -27,7 +26,7 @@ IndexBufferContext(PreparedGraphicsObjects *pgo, GeomPrimitive *data) : */ INLINE GeomPrimitive *IndexBufferContext:: get_data() const { - return _data; + return (GeomPrimitive *)_object; } /** @@ -36,7 +35,7 @@ get_data() const { */ INLINE bool IndexBufferContext:: changed_size(const GeomPrimitivePipelineReader *reader) const { - nassertr(reader->get_object() == _data, false); + nassertr(reader->get_object() == get_data(), false); return get_data_size_bytes() != (size_t)reader->get_data_size_bytes(); } @@ -46,7 +45,7 @@ changed_size(const GeomPrimitivePipelineReader *reader) const { */ INLINE bool IndexBufferContext:: changed_usage_hint(const GeomPrimitivePipelineReader *reader) const { - nassertr(reader->get_object() == _data, false); + nassertr(reader->get_object() == get_data(), false); return _usage_hint != reader->get_usage_hint(); } @@ -56,7 +55,7 @@ changed_usage_hint(const GeomPrimitivePipelineReader *reader) const { */ INLINE bool IndexBufferContext:: was_modified(const GeomPrimitivePipelineReader *reader) const { - nassertr(reader->get_object() == _data, false); + nassertr(reader->get_object() == get_data(), false); return get_modified() != reader->get_modified(); } @@ -76,7 +75,7 @@ update_data_size_bytes(size_t new_data_size_bytes) { */ INLINE void IndexBufferContext:: mark_loaded(const GeomPrimitivePipelineReader *reader) { - nassertv(reader->get_object() == _data); + nassertv(reader->get_object() == get_data()); update_data_size_bytes(reader->get_data_size_bytes()); update_modified(reader->get_modified()); _usage_hint = reader->get_usage_hint(); diff --git a/panda/src/gobj/indexBufferContext.h b/panda/src/gobj/indexBufferContext.h index 6b2004fac2..139ab69362 100644 --- a/panda/src/gobj/indexBufferContext.h +++ b/panda/src/gobj/indexBufferContext.h @@ -50,9 +50,6 @@ public: virtual void write(std::ostream &out, int indent_level) const; private: - // This cannot be a PT(GeomPrimitive), because the data and the GSG both own - // their IndexBufferContexts! That would create a circular reference count. - GeomPrimitive *_data; GeomEnums::UsageHint _usage_hint; public: diff --git a/panda/src/gobj/preparedGraphicsObjects.cxx b/panda/src/gobj/preparedGraphicsObjects.cxx index 96fcd80269..e3ab0e8d83 100644 --- a/panda/src/gobj/preparedGraphicsObjects.cxx +++ b/panda/src/gobj/preparedGraphicsObjects.cxx @@ -270,11 +270,11 @@ void PreparedGraphicsObjects:: release_texture(TextureContext *tc) { ReMutexHolder holder(_lock); - tc->_texture->clear_prepared(tc->get_view(), this); + tc->get_texture()->clear_prepared(tc->get_view(), this); // We have to set the Texture pointer to NULL at this point, since the // Texture itself might destruct at any time after it has been released. - tc->_texture = nullptr; + tc->_object = nullptr; bool removed = (_prepared_textures.erase(tc) != 0); nassertv(removed); @@ -307,8 +307,8 @@ release_all_textures() { tci != _prepared_textures.end(); ++tci) { TextureContext *tc = (*tci); - tc->_texture->clear_prepared(tc->get_view(), this); - tc->_texture = nullptr; + tc->get_texture()->clear_prepared(tc->get_view(), this); + tc->_object = nullptr; _released_textures.insert(tc); } @@ -946,14 +946,14 @@ void PreparedGraphicsObjects:: release_vertex_buffer(VertexBufferContext *vbc) { ReMutexHolder holder(_lock); - vbc->_data->clear_prepared(this); + vbc->get_data()->clear_prepared(this); - size_t data_size_bytes = vbc->_data->get_data_size_bytes(); - GeomEnums::UsageHint usage_hint = vbc->_data->get_usage_hint(); + size_t data_size_bytes = vbc->get_data()->get_data_size_bytes(); + GeomEnums::UsageHint usage_hint = vbc->get_data()->get_usage_hint(); // We have to set the Data pointer to NULL at this point, since the Data // itself might destruct at any time after it has been released. - vbc->_data = nullptr; + vbc->_object = nullptr; bool removed = (_prepared_vertex_buffers.erase(vbc) != 0); nassertv(removed); @@ -985,8 +985,8 @@ release_all_vertex_buffers() { vbci != _prepared_vertex_buffers.end(); ++vbci) { VertexBufferContext *vbc = (VertexBufferContext *)(*vbci); - vbc->_data->clear_prepared(this); - vbc->_data = nullptr; + vbc->get_data()->clear_prepared(this); + vbc->_object = nullptr; _released_vertex_buffers.insert(vbc); } @@ -1061,7 +1061,7 @@ prepare_vertex_buffer_now(GeomVertexArrayData *data, GraphicsStateGuardianBase * _vertex_buffer_cache, _vertex_buffer_cache_lru, _vertex_buffer_cache_size); if (vbc != nullptr) { - vbc->_data = data; + vbc->_object = data; } else { // Ask the GSG to create a brand new VertexBufferContext. There might be @@ -1144,14 +1144,14 @@ void PreparedGraphicsObjects:: release_index_buffer(IndexBufferContext *ibc) { ReMutexHolder holder(_lock); - ibc->_data->clear_prepared(this); + ibc->get_data()->clear_prepared(this); - size_t data_size_bytes = ibc->_data->get_data_size_bytes(); - GeomEnums::UsageHint usage_hint = ibc->_data->get_usage_hint(); + size_t data_size_bytes = ibc->get_data()->get_data_size_bytes(); + GeomEnums::UsageHint usage_hint = ibc->get_data()->get_usage_hint(); // We have to set the Data pointer to NULL at this point, since the Data // itself might destruct at any time after it has been released. - ibc->_data = nullptr; + ibc->_object = nullptr; bool removed = (_prepared_index_buffers.erase(ibc) != 0); nassertv(removed); @@ -1183,8 +1183,8 @@ release_all_index_buffers() { ibci != _prepared_index_buffers.end(); ++ibci) { IndexBufferContext *ibc = (IndexBufferContext *)(*ibci); - ibc->_data->clear_prepared(this); - ibc->_data = nullptr; + ibc->get_data()->clear_prepared(this); + ibc->_object = nullptr; _released_index_buffers.insert(ibc); } @@ -1258,7 +1258,7 @@ prepare_index_buffer_now(GeomPrimitive *data, GraphicsStateGuardianBase *gsg) { _index_buffer_cache, _index_buffer_cache_lru, _index_buffer_cache_size); if (ibc != nullptr) { - ibc->_data = data; + ibc->_object = data; } else { // Ask the GSG to create a brand new IndexBufferContext. There might be @@ -1341,6 +1341,13 @@ void PreparedGraphicsObjects:: release_shader_buffer(BufferContext *bc) { ReMutexHolder holder(_lock); + ShaderBuffer *buffer = (ShaderBuffer *)bc->_object; + buffer->clear_prepared(this); + + // We have to set the ShaderBuffer pointer to NULL at this point, since the + // buffer itself might destruct at any time after it has been released. + bc->_object = nullptr; + bool removed = (_prepared_shader_buffers.erase(bc) != 0); nassertv(removed); diff --git a/panda/src/gobj/shaderBuffer.cxx b/panda/src/gobj/shaderBuffer.cxx index 4e21319b49..fa16293d4d 100644 --- a/panda/src/gobj/shaderBuffer.cxx +++ b/panda/src/gobj/shaderBuffer.cxx @@ -143,6 +143,31 @@ release_all() { return num_freed; } +/** + * Removes the indicated PreparedGraphicsObjects table from the buffer's + * table, without actually releasing the texture. This is intended to be + * called only from PreparedGraphicsObjects::release_shader_buffer(); it + * should never be called by user code. + */ +void ShaderBuffer:: +clear_prepared(PreparedGraphicsObjects *prepared_objects) { + nassertv(_contexts != nullptr); + + Contexts::iterator ci; + ci = _contexts->find(prepared_objects); + if (ci != _contexts->end()) { + _contexts->erase(ci); + if (_contexts->empty()) { + delete _contexts; + _contexts = nullptr; + } + } else { + // If this assertion fails, clear_prepared() was given a prepared_objects + // which the data array didn't know about. + nassert_raise("unknown PreparedGraphicsObjects"); + } +} + /** * Tells the BamReader how to create objects of type ParamValue. */ diff --git a/panda/src/gobj/shaderBuffer.h b/panda/src/gobj/shaderBuffer.h index bdf0538857..0cc0660433 100644 --- a/panda/src/gobj/shaderBuffer.h +++ b/panda/src/gobj/shaderBuffer.h @@ -57,6 +57,9 @@ PUBLISHED: bool release(PreparedGraphicsObjects *prepared_objects); int release_all(); +private: + void clear_prepared(PreparedGraphicsObjects *prepared_objects); + private: uint64_t _data_size_bytes; UsageHint _usage_hint; @@ -91,6 +94,8 @@ public: private: static TypeHandle _type_handle; + + friend class PreparedGraphicsObjects; }; INLINE std::ostream &operator << (std::ostream &out, const ShaderBuffer &m) { diff --git a/panda/src/gobj/textureContext.I b/panda/src/gobj/textureContext.I index fa947755d0..2c58bc7b07 100644 --- a/panda/src/gobj/textureContext.I +++ b/panda/src/gobj/textureContext.I @@ -16,9 +16,8 @@ */ INLINE TextureContext:: TextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view) : - BufferContext(&pgo->_texture_residency), + BufferContext(&pgo->_texture_residency, tex), AdaptiveLruPage(0), - _texture(tex), _view(view) { } @@ -28,7 +27,7 @@ TextureContext(PreparedGraphicsObjects *pgo, Texture *tex, int view) : */ INLINE Texture *TextureContext:: get_texture() const { - return _texture; + return (Texture *)_object; } /** @@ -56,7 +55,7 @@ was_modified() const { */ INLINE bool TextureContext:: was_properties_modified() const { - return _properties_modified != _texture->get_properties_modified(); + return _properties_modified != get_texture()->get_properties_modified(); } /** @@ -65,7 +64,7 @@ was_properties_modified() const { */ INLINE bool TextureContext:: was_image_modified() const { - return _image_modified != _texture->get_image_modified(); + return _image_modified != get_texture()->get_image_modified(); } /** @@ -74,7 +73,7 @@ was_image_modified() const { */ INLINE bool TextureContext:: was_simple_image_modified() const { - return _simple_image_modified != _texture->get_simple_image_modified(); + return _simple_image_modified != get_texture()->get_simple_image_modified(); } /** @@ -121,8 +120,8 @@ update_data_size_bytes(size_t new_data_size_bytes) { INLINE void TextureContext:: mark_loaded() { // _data_size_bytes = _data->get_texture_size_bytes(); - _properties_modified = _texture->get_properties_modified(); - _image_modified = _texture->get_image_modified(); + _properties_modified = get_texture()->get_properties_modified(); + _image_modified = get_texture()->get_image_modified(); update_modified(std::max(_properties_modified, _image_modified)); // Assume the texture is now resident. @@ -135,8 +134,8 @@ mark_loaded() { */ INLINE void TextureContext:: mark_simple_loaded() { - _properties_modified = _texture->get_properties_modified(); - _simple_image_modified = _texture->get_simple_image_modified(); + _properties_modified = get_texture()->get_properties_modified(); + _simple_image_modified = get_texture()->get_simple_image_modified(); update_modified(std::max(_properties_modified, _simple_image_modified)); // The texture's not exactly resident now, but some part of it is. diff --git a/panda/src/gobj/textureContext.h b/panda/src/gobj/textureContext.h index c7ae367ccc..2962fd38c0 100644 --- a/panda/src/gobj/textureContext.h +++ b/panda/src/gobj/textureContext.h @@ -60,9 +60,6 @@ public: virtual void write(std::ostream &out, int indent_level) const; private: - // This cannot be a PT(Texture), because the texture and the GSG both own - // their TextureContexts! That would create a circular reference count. - Texture *_texture; int _view; UpdateSeq _properties_modified; UpdateSeq _image_modified; diff --git a/panda/src/gobj/vertexBufferContext.I b/panda/src/gobj/vertexBufferContext.I index b57739cf09..bbfff0bb27 100644 --- a/panda/src/gobj/vertexBufferContext.I +++ b/panda/src/gobj/vertexBufferContext.I @@ -16,9 +16,8 @@ */ INLINE VertexBufferContext:: VertexBufferContext(PreparedGraphicsObjects *pgo, GeomVertexArrayData *data) : - BufferContext(&pgo->_vbuffer_residency), - AdaptiveLruPage(0), - _data(data) + BufferContext(&pgo->_vbuffer_residency, data), + AdaptiveLruPage(0) { } @@ -27,7 +26,7 @@ VertexBufferContext(PreparedGraphicsObjects *pgo, GeomVertexArrayData *data) : */ INLINE GeomVertexArrayData *VertexBufferContext:: get_data() const { - return _data; + return (GeomVertexArrayData *)_object; } /** @@ -36,7 +35,7 @@ get_data() const { */ INLINE bool VertexBufferContext:: changed_size(const GeomVertexArrayDataHandle *reader) const { - nassertr(reader->get_object() == _data, false); + nassertr(reader->get_object() == get_data(), false); return get_data_size_bytes() != (size_t)reader->get_data_size_bytes(); } @@ -46,7 +45,7 @@ changed_size(const GeomVertexArrayDataHandle *reader) const { */ INLINE bool VertexBufferContext:: changed_usage_hint(const GeomVertexArrayDataHandle *reader) const { - nassertr(reader->get_object() == _data, false); + nassertr(reader->get_object() == get_data(), false); return _usage_hint != reader->get_usage_hint(); } @@ -56,7 +55,7 @@ changed_usage_hint(const GeomVertexArrayDataHandle *reader) const { */ INLINE bool VertexBufferContext:: was_modified(const GeomVertexArrayDataHandle *reader) const { - nassertr(reader->get_object() == _data, false); + nassertr(reader->get_object() == get_data(), false); return get_modified() != reader->get_modified(); } @@ -77,7 +76,7 @@ update_data_size_bytes(size_t new_data_size_bytes) { */ INLINE void VertexBufferContext:: mark_loaded(const GeomVertexArrayDataHandle *reader) { - nassertv(reader->get_object() == _data); + nassertv(reader->get_object() == get_data()); update_data_size_bytes(reader->get_data_size_bytes()); update_modified(reader->get_modified()); _usage_hint = reader->get_usage_hint(); diff --git a/panda/src/gobj/vertexBufferContext.h b/panda/src/gobj/vertexBufferContext.h index 69f02e0ed7..c1696ba6cb 100644 --- a/panda/src/gobj/vertexBufferContext.h +++ b/panda/src/gobj/vertexBufferContext.h @@ -51,10 +51,6 @@ public: virtual void write(std::ostream &out, int indent_level) const; private: - // This cannot be a PT(GeomVertexArrayData), because the data and the GSG - // both own their VertexBufferContexts! That would create a circular - // reference count. - GeomVertexArrayData *_data; GeomEnums::UsageHint _usage_hint; public: From 6b27dae24deaa42d53895231a6a247ae465cfdec Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 26 Dec 2018 22:54:01 +0100 Subject: [PATCH 380/424] deploy-ng: add hidden imports for numpy on macOS --- direct/src/dist/FreezeTool.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index f014a7bd9e..5b2cd99043 100644 --- a/direct/src/dist/FreezeTool.py +++ b/direct/src/dist/FreezeTool.py @@ -76,6 +76,11 @@ hiddenImports = { 'keyring.backends': ['keyring.backends.*'], 'matplotlib.font_manager': ['encodings.mac_roman'], 'direct.particles': ['direct.particles.ParticleManagerGlobal'], + 'numpy.core._multiarray_umath': [ + 'numpy.core._internal', + 'numpy.core._dtype_ctypes', + 'numpy.core._methods', + ], } if sys.version_info >= (3,): From 3a2a29a1aa2fbafe6ea482d190c8692b77f82a68 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 26 Dec 2018 22:54:19 +0100 Subject: [PATCH 381/424] deploy-ng: catch more egg2bam errors with Python 2.7 --- direct/src/dist/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index ec69bf996f..649b95c2f1 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -65,7 +65,7 @@ def egg2bam(_build_cmd, srcpath, dstpath): ]) except FileNotFoundError: raise RuntimeError('egg2bam failed: egg2bam was not found in the PATH') - except subprocess.CalledProcessError as err: + except (subprocess.CalledProcessError, OSError) as err: raise RuntimeError('egg2bam failed: {}'.format(err)) return dstpath From b7e7986198572ad6682198f2e59ea5d328759b96 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 26 Dec 2018 23:18:17 +0100 Subject: [PATCH 382/424] deploy-ng: rewrite relative dependencies in macOS dylibs --- direct/src/dist/commands.py | 52 +++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index 649b95c2f1..a134aad64f 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -624,7 +624,7 @@ class build_apps(setuptools.Command): search_path = [builddir] if use_wheels: search_path.append(os.path.join(p3dwhlfn, 'deploy_libs')) - self.copy_dependencies(open(target_path, 'rb'), builddir, search_path, stub_name) + self.copy_dependencies(target_path, builddir, search_path, stub_name) freezer_extras.update(freezer.extras) freezer_modules.update(freezer.getAllModuleNames()) @@ -869,7 +869,7 @@ class build_apps(setuptools.Command): return for dep in self.exclude_dependencies: - if dep.matches_file(name): + if dep.matches_file(name): return for dir in search_path: @@ -957,14 +957,15 @@ class build_apps(setuptools.Command): self.copy(source_path, target_path) - fp = open(target_path, 'rb') source_dir = os.path.dirname(source_path) target_dir = os.path.dirname(target_path) base = os.path.basename(target_path) - self.copy_dependencies(fp, target_dir, search_path + [source_dir], base) + self.copy_dependencies(target_path, target_dir, search_path + [source_dir], base) - def copy_dependencies(self, fp, target_dir, search_path, referenced_by): - """ Copies the dependencies of the given open file. """ + def copy_dependencies(self, target_path, target_dir, search_path, referenced_by): + """ Copies the dependencies of target_path into target_dir. """ + + fp = open(target_path, 'rb+') # What kind of magic does the file contain? deps = [] @@ -983,20 +984,21 @@ class build_apps(setuptools.Command): elif magic in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'): # A Mach-O file, as used on macOS. - deps = self._read_dependencies_macho(fp, '<') + deps = self._read_dependencies_macho(fp, '<', flatten=True) elif magic in (b'\xFE\xED\xFA\xCE', b'\xFE\xED\xFA\xCF'): - deps = self._read_dependencies_macho(fp, '>') + rel_dir = os.path.relpath(target_dir, os.path.dirname(target_path)) + deps = self._read_dependencies_macho(fp, '>', flatten=True) elif magic in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA'): # A fat file, containing multiple Mach-O binaries. In the future, # we may want to extract the one containing the architecture we # are building for. - deps = self._read_dependencies_fat(fp, False) + deps = self._read_dependencies_fat(fp, False, flatten=True) elif magic in (b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'): # A 64-bit fat file. - deps = self._read_dependencies_fat(fp, True) + deps = self._read_dependencies_fat(fp, True, flatten=True) # If we discovered any dependencies, recursively add those. for dep in deps: @@ -1065,9 +1067,14 @@ class build_apps(setuptools.Command): search_path += rpath return needed - def _read_dependencies_macho(self, fp, endian): + def _read_dependencies_macho(self, fp, endian, flatten=False): """ Having read the first 4 bytes of the Mach-O file, fetches the - dependent libraries and returns those as a list. """ + dependent libraries and returns those as a list. + + If flatten is True, if the dependencies contain paths like + @loader_path/../.dylibs/libsomething.dylib, it will rewrite them to + instead contain @loader_path/libsomething.dylib if possible. + This requires the file pointer to be opened in rb+ mode. """ cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \ struct.unpack(endian + 'IIIIII', fp.read(24)) @@ -1086,17 +1093,30 @@ class build_apps(setuptools.Command): if cmd == 0x0c: # LC_LOAD_DYLIB dylib = cmd_data[16:].decode('ascii').split('\x00', 1)[0] + orig = dylib + if dylib.startswith('@loader_path/../Frameworks/'): dylib = dylib.replace('@loader_path/../Frameworks/', '') - if dylib.startswith('@executable_path/../Frameworks/'): + elif dylib.startswith('@executable_path/../Frameworks/'): dylib = dylib.replace('@executable_path/../Frameworks/', '') - if dylib.startswith('@loader_path/'): + elif dylib.startswith('@loader_path/'): dylib = dylib.replace('@loader_path/', '') + + # Do we need to flatten the relative reference? + if '/' in dylib and flatten: + new_dylib = '@loader_path/' + os.path.basename(dylib) + str_size = len(cmd_data) - 16 + if len(new_dylib) < str_size: + fp.seek(-str_size, os.SEEK_CUR) + fp.write(new_dylib.encode('ascii').ljust(str_size, b'\0')) + else: + self.warn('Unable to rewrite dependency {}'.format(orig)) + load_dylibs.append(dylib) return load_dylibs - def _read_dependencies_fat(self, fp, is_64bit): + def _read_dependencies_fat(self, fp, is_64bit, flatten=False): num_fat, = struct.unpack('>I', fp.read(4)) # After the header we get a table of executables in this fat file, @@ -1126,7 +1146,7 @@ class build_apps(setuptools.Command): # Not a Mach-O file we can read. continue - for dep in self._read_dependencies_macho(fp, endian): + for dep in self._read_dependencies_macho(fp, endian, flatten=flatten): if dep not in deps: deps.append(dep) From 981c44f636113d798993f7e8b86f32a291bc62c8 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 26 Dec 2018 23:31:48 +0100 Subject: [PATCH 383/424] glgsg: fix misleading errors from p3d_LightSource[].shadowMap --- panda/src/glstuff/glShaderContext_src.cxx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx index bf6c6097fa..ae7a845efc 100644 --- a/panda/src/glstuff/glShaderContext_src.cxx +++ b/panda/src/glstuff/glShaderContext_src.cxx @@ -2797,14 +2797,24 @@ update_shader_texture_bindings(ShaderContext *prev) { } if (tex->get_texture_type() != spec._desired_type) { - if (id != nullptr) { + switch (spec._part) { + case Shader::STO_named_input: GLCAT.error() << "Sampler type of GLSL shader input '" << *id << "' does not " "match type of texture " << *tex << ".\n"; - } else { + break; + + case Shader::STO_stage_i: GLCAT.error() << "Sampler type of GLSL shader input p3d_Texture" << spec._stage << " does not match type of texture " << *tex << ".\n"; + break; + + case Shader::STO_light_i_shadow_map: + GLCAT.error() + << "Sampler type of GLSL shader input p3d_LightSource[" << spec._stage + << "].shadowMap does not match type of texture " << *tex << ".\n"; + break; } // TODO: also check whether shadow sampler textures have shadow filter // enabled. From ae74e2d18e1ba8e0931b74db15a8a440ba8284f7 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 26 Dec 2018 23:42:39 +0100 Subject: [PATCH 384/424] makepackage: avoid packaging deploy-stub with installers --- makepanda/installer.nsi | 2 +- makepanda/installpanda.py | 5 ++++- makepanda/makepackage.py | 7 ++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/makepanda/installer.nsi b/makepanda/installer.nsi index c3f9745b4a..60de4bcc9d 100644 --- a/makepanda/installer.nsi +++ b/makepanda/installer.nsi @@ -367,7 +367,7 @@ Section "Tools and utilities" SecTools SetDetailsPrint listonly SetOutPath "$INSTDIR\bin" - File /r "${BUILT}\bin\*.exe" + File /r /x deploy-stub.exe /x deploy-stubw.exe "${BUILT}\bin\*.exe" File /nonfatal /r "${BUILT}\bin\*.p3d" SetOutPath "$INSTDIR\NSIS" File /r /x CVS "${NSISDIR}\*" diff --git a/makepanda/installpanda.py b/makepanda/installpanda.py index fe5ee97563..7f9b79cd60 100644 --- a/makepanda/installpanda.py +++ b/makepanda/installpanda.py @@ -220,12 +220,15 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir( else: oscmd("echo '"+libdir+"/panda3d'> "+destdir+"/etc/ld.so.conf.d/panda3d.conf") - oscmd("cp "+outputdir+"/bin/* "+destdir+prefix+"/bin/") for base in os.listdir(outputdir+"/lib"): if (not base.endswith(".a")) or base == "libp3pystub.a": # We really need to specify -R in order not to follow symlinks on non-GNU oscmd("cp -R -P "+outputdir+"/lib/"+base+" "+destdir+libdir+"/panda3d/"+base) + for base in os.listdir(outputdir+"/bin"): + if not base.startswith("deploy-stub"): + oscmd("cp -R -P "+outputdir+"/bin/"+base+" "+destdir+prefix+"/bin/"+base) + DeleteVCS(destdir+prefix+"/share/panda3d") DeleteBuildFiles(destdir+prefix+"/share/panda3d") DeleteEmptyDirs(destdir+prefix+"/share/panda3d") diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index e23bedffd7..a4eadaf38a 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -512,9 +512,10 @@ def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs): oscmd("install -m 0644 doc/man/*.1 dstroot/tools/usr/local/share/man/man1/") for base in os.listdir(outputdir+"/bin"): - binname = "dstroot/tools/Developer/Panda3D/bin/" + base - # OSX needs the -R argument to copy symbolic links correctly, it doesn't have -d. How weird. - oscmd("cp -R " + outputdir + "/bin/" + base + " " + binname) + if not base.startswith("deploy-stub"): + binname = "dstroot/tools/Developer/Panda3D/bin/" + base + # OSX needs the -R argument to copy symbolic links correctly, it doesn't have -d. How weird. + oscmd("cp -R " + outputdir + "/bin/" + base + " " + binname) if python_versions: # Let's only write a ppython link if there is only one Python version. From ee6531abee542a88bbe269a6309808d6acc2b057 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 26 Dec 2018 23:45:00 +0100 Subject: [PATCH 385/424] makepackage: copy over panda3d.dist-info dir on macOS --- makepanda/makepackage.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index a4eadaf38a..0747169df5 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -554,6 +554,10 @@ def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs): oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver)) WriteFile("dstroot/pybindings%s/Library/Python/%s/site-packages/Panda3D.pth" % (pyver, pyver), "/Developer/Panda3D") + # Copy over panda3d.dist-info directory. + if os.path.isdir(outputdir + "/panda3d.dist-info"): + oscmd("cp -R %s/panda3d.dist-info dstroot/pybindings%s/Library/Python/%s/site-packages/" % (outputdir, pyver, pyver)) + if not PkgSkip("FFMPEG"): oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib") oscmd("cp -R %s/lib/libp3ffmpeg.* dstroot/ffmpeg/Developer/Panda3D/lib/" % outputdir) From 7bdbbc78a3085ae6caefcc92ac8894cc6720b55b Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Dec 2018 15:35:43 +0100 Subject: [PATCH 386/424] tests: fix syntax error in setup.py, use default platforms --- tests/setup.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/setup.py b/tests/setup.py index a8ce20715e..b5967f3b14 100644 --- a/tests/setup.py +++ b/tests/setup.py @@ -3,7 +3,7 @@ import pytest setup( name="panda3d-tester", - options = { + options={ 'build_apps': { 'gui_apps': { 'tester': 'main.py', @@ -12,13 +12,6 @@ setup( 'pandagl', 'p3openal_audio', ], - }, - 'platforms': [ - 'manylinux1_x86_64', - 'macosx_10_6_x86_64', - 'win32', - 'win_amd64', - ], } } ) From 30446952cb86e88abacdc788cab1ba3b773ebc3b Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Dec 2018 21:09:29 +0100 Subject: [PATCH 387/424] putil: remove no-longer-used WeakKeyHashMap for now --- panda/src/putil/p3putil_composite2.cxx | 1 - panda/src/putil/weakKeyHashMap.I | 627 ------------------------- panda/src/putil/weakKeyHashMap.cxx | 14 - panda/src/putil/weakKeyHashMap.h | 108 ----- 4 files changed, 750 deletions(-) delete mode 100644 panda/src/putil/weakKeyHashMap.I delete mode 100644 panda/src/putil/weakKeyHashMap.cxx delete mode 100644 panda/src/putil/weakKeyHashMap.h diff --git a/panda/src/putil/p3putil_composite2.cxx b/panda/src/putil/p3putil_composite2.cxx index 80ae7450bb..2b907ef7f7 100644 --- a/panda/src/putil/p3putil_composite2.cxx +++ b/panda/src/putil/p3putil_composite2.cxx @@ -25,7 +25,6 @@ #include "vector_typedWritable.cxx" #include "vector_ushort.cxx" #include "vector_writable.cxx" -#include "weakKeyHashMap.cxx" #include "writableConfigurable.cxx" #include "writableParam.cxx" diff --git a/panda/src/putil/weakKeyHashMap.I b/panda/src/putil/weakKeyHashMap.I deleted file mode 100644 index b2dc97c3f4..0000000000 --- a/panda/src/putil/weakKeyHashMap.I +++ /dev/null @@ -1,627 +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 weakKeyHashMap.I - * @author rdb - * @date 2015-07-13 - */ - -/** - * - */ -template -INLINE WeakKeyHashMap:: -WeakKeyHashMap() : - _table(nullptr), - _deleted_chain(nullptr), - _table_size(0), - _num_entries(0) -{ -} - -/** - * - */ -template -INLINE WeakKeyHashMap:: -~WeakKeyHashMap() { - clear(); -} - -/** - * Quickly exchanges the contents of this map and the other map. - */ -template -INLINE void WeakKeyHashMap:: -swap(WeakKeyHashMap &other) { - TableEntry *t0 = _table; - _table = other._table; - other._table = t0; - - DeletedBufferChain *t1 = _deleted_chain; - _deleted_chain = other._deleted_chain; - other._deleted_chain = t1; - - size_t t2 = _table_size; - _table_size = other._table_size; - other._table_size = t2; - - size_t t3 = _num_entries; - _num_entries = other._num_entries; - other._num_entries = t3; -} - -/** - * Searches for the indicated key in the table. Returns its index number if - * it is found, or -1 if it is not present in the table. - */ -template -int WeakKeyHashMap:: -find(const Key *key) const { - if (_table_size == 0) { - // Special case: the table is empty. - return -1; - } - - size_t index = get_hash(key); - if (!has_element(index)) { - return -1; - } - if (is_element(index, key)) { - return index; - } - - // There was some other key at the hashed slot. That's a hash conflict. - // Maybe our entry was recorded at a later slot position; scan the - // subsequent positions until we find the entry or an unused slot, - // indicating the end of the scan. - size_t i = index; - i = (i + 1) & (_table_size - 1); - while (i != index && has_element(i)) { - if (is_element(i, key)) { - return i; - } - i = (i + 1) & (_table_size - 1); - } - - // The key is not in the table. - return -1; -} - -/** - * Records the indicated key/data pair in the map. If the key was already - * present, silently replaces it. Returns the index at which it was stored. - */ -template -int WeakKeyHashMap:: -store(const Key *key, const Value &data) { - if (_table_size == 0) { - // Special case: the first key in an empty table. - nassertr(_num_entries == 0, -1); - new_table(); - size_t index = get_hash(key); - store_new_element(index, key, data); - ++_num_entries; -#ifdef _DEBUG - nassertr(validate(), index); -#endif - return index; - } - - size_t index = get_hash(key); - if (!has_element(index)) { - // This element is not already in the map; add it. - if (consider_expand_table()) { - return store(key, data); - } - store_new_element(index, key, data); - ++_num_entries; -#ifdef _DEBUG - nassertr(validate(), index); -#endif - return index; - } - if (is_element(index, key)) { - // This element is already in the map; replace the data at that key. - _table[index]._data = data; -#ifdef _DEBUG - nassertr(validate(), index); -#endif - return index; - } - - // There was some other key at the hashed slot. That's a hash conflict. - // Record this entry at a later position. - size_t i = index; - i = (i + 1) & (_table_size - 1); - while (i != index) { - if (!has_element(i)) { - if (consider_expand_table()) { - return store(key, data); - } - store_new_element(i, key, data); - ++_num_entries; -#ifdef _DEBUG - nassertr(validate(), i); -#endif - return i; - } - if (is_element(i, key)) { - _table[i]._data = data; -#ifdef _DEBUG - nassertr(validate(), i); -#endif - return i; - } - i = (i + 1) & (_table_size - 1); - } - - // Shouldn't get here unless _num_entries == _table_size, which shouldn't be - // possible due to consider_expand_table(). - nassertr(false, -1); - return -1; // To satisfy compiler -} - -/** - * Removes the indicated key and its associated data from the table. Returns - * true if the key was removed, false if it was not present. - */ -template -INLINE bool WeakKeyHashMap:: -remove(const Key *key) { - int index = find(key); - if (index == -1) { - return false; - } - remove_element(index); - return true; -} - -/** - * Completely empties the table. - */ -template -void WeakKeyHashMap:: -clear() { - if (_table_size != 0) { - for (size_t i = 0; i < _table_size; ++i) { - if (get_exists_array()[i] != 0) { - clear_element(i); - } - } - - _deleted_chain->deallocate(_table, TypeHandle::none()); - _table = nullptr; - _deleted_chain = nullptr; - _table_size = 0; - _num_entries = 0; - } -} - -/** - * Returns a modifiable reference to the data associated with the indicated - * key, or creates a new data entry and returns its reference. - */ -template -INLINE Value &WeakKeyHashMap:: -operator [] (const Key *key) { - int index = find(key); - if (index == -1) { - index = store(key, Value()); - } - return modify_data(index); -} - -/** - * Returns the total number of slots in the table. - */ -template -INLINE size_t WeakKeyHashMap:: -get_size() const { - return _table_size; -} - -/** - * Returns true if there is an element stored in the nth slot, false - * otherwise. - * - * n should be in the range 0 <= n < get_size(). - */ -template -INLINE bool WeakKeyHashMap:: -has_element(size_t n) const { - nassertr(n < _table_size, false); - return (get_exists_array()[n] != 0 && !_table[n]._key.was_deleted()); -} - -/** - * Returns the key in the nth slot of the table. - * - * It is an error to call this if there is nothing stored in the nth slot (use - * has_element() to check this first). n should be in the range 0 <= n < - * get_size(). - */ -template -INLINE const Key *WeakKeyHashMap:: -get_key(size_t n) const { - nassertr(has_element(n), _table[n]._key); - return _table[n]._key; -} - -/** - * Returns the data in the nth slot of the table. - * - * It is an error to call this if there is nothing stored in the nth slot (use - * has_element() to check this first). n should be in the range 0 <= n < - * get_size(). - */ -template -INLINE const Value &WeakKeyHashMap:: -get_data(size_t n) const { - nassertr(has_element(n), _table[n]._data); - return _table[n]._data; -} - -/** - * Returns a modifiable reference to the data in the nth slot of the table. - * - * It is an error to call this if there is nothing stored in the nth slot (use - * has_element() to check this first). n should be in the range 0 <= n < - * get_size(). - */ -template -INLINE Value &WeakKeyHashMap:: -modify_data(size_t n) { - nassertr(has_element(n), _table[n]._data); - return _table[n]._data; -} - -/** - * Changes the data for the nth slot of the table. - * - * It is an error to call this if there is nothing stored in the nth slot (use - * has_element() to check this first). n should be in the range 0 <= n < - * get_size(). - */ -template -INLINE void WeakKeyHashMap:: -set_data(size_t n, const Value &data) { - nassertv(has_element(n)); - _table[n]._data = data; -} - -/** - * Changes the data for the nth slot of the table. - * - * It is an error to call this if there is nothing stored in the nth slot (use - * has_element() to check this first). n should be in the range 0 <= n < - * get_size(). - */ -template -INLINE void WeakKeyHashMap:: -set_data(size_t n, Value &&data) { - nassertv(has_element(n)); - _table[n]._data = std::move(data); -} - -/** - * Removes the nth slot from the table. - * - * It is an error to call this if there is nothing stored in the nth slot (use - * has_element() to check this first). n should be in the range 0 <= n < - * get_size(). - */ -template -void WeakKeyHashMap:: -remove_element(size_t n) { - nassertv(get_exists_array()[n] != 0); - - clear_element(n); - nassertv(_num_entries > 0); - --_num_entries; - - // Now we have put a hole in the table. If there was a hash conflict in the - // slot following this one, we have to move it down to close the hole. - size_t i = n; - i = (i + 1) & (_table_size - 1); - while (get_exists_array()[i] != 0) { - if (_table[i]._key.was_deleted()) { - // It was deleted. Forget about it. - clear_element(i); - --_num_entries; - } else { - size_t wants_index = get_hash(_table[i]._key.get_orig()); - if (wants_index != i) { - // This one was a hash conflict; try to put it where it belongs. We - // can't just put it in n, since maybe it belongs somewhere after n. - while (wants_index != i && has_element(wants_index)) { - // Hash conflict; move it up. - wants_index = (wants_index + 1) & (_table_size - 1); - } - if (wants_index != i) { - store_new_element(wants_index, _table[i]._key, _table[i]._data); - clear_element(i); - } - } - } - - // Continue until we encounter the next unused slot. Until we do, we - // can't be sure we've found all of the potential hash conflicts. - i = (i + 1) & (_table_size - 1); - } - -#ifdef _DEBUG - nassertv(validate()); -#endif -} - -/** - * Returns the number of active entries in the table. This is not necessarily - * related to the number of slots in the table as reported by get_size(). Use - * get_size() to iterate through all of the slots, not get_num_entries(). - * - * This is merely an upper bound on the number of entries; it may also count - * false positives for pointers that were recently deleted. - */ -template -INLINE size_t WeakKeyHashMap:: -get_num_entries() const { - return _num_entries; -} - -/** - * Returns true if the table is empty; i.e. get_num_entries() == 0. This may - * return a false negatives if a pointer was recently deleted; if this returns - * true, though, you can be sure it's empty. - */ -template -INLINE bool WeakKeyHashMap:: -is_empty() const { - return (_num_entries == 0); -} - -/** - * - */ -template -void WeakKeyHashMap:: -output(std::ostream &out) const { - out << "WeakKeyHashMap (" << _num_entries << " entries): ["; - for (size_t i = 0; i < _table_size; ++i) { - if (get_exists_array()[i] == 0) { - out << " *"; - - } else { - out << " " << _table[i]._key; - size_t index = get_hash(_table[i]._key.get_orig()); - if (index != i) { - // This was misplaced as the result of a hash conflict. Report how - // far off it is. - out << "(" << ((_table_size + i - index) & (_table_size - 1)) << ")"; - } - } - } - out << " ]"; -} - -/** - * - */ -template -void WeakKeyHashMap:: -write(std::ostream &out) const { - output(out); - out << "\n"; -} - -/** - * Returns true if the internal table appears to be consistent, false if there - * are some internal errors. - */ -template -bool WeakKeyHashMap:: -validate() const { - size_t count = 0; - - const unsigned char *exists_array = get_exists_array(); - - for (size_t i = 0; i < _table_size; ++i) { - if (exists_array[i] != 0) { - ++count; - if (_table[i]._key.was_deleted()) { - continue; - } - size_t ideal_index = get_hash(_table[i]._key.get_orig()); - size_t wants_index = ideal_index; - while (wants_index != i && exists_array[wants_index] != 0) { - wants_index = (wants_index + 1) & (_table_size - 1); - } - if (wants_index != i) { - util_cat.error() - << "WeakKeyHashMap is invalid: key " << _table[i]._key - << " should be in slot " << wants_index << " instead of " - << i << " (ideal is " << ideal_index << ")\n"; - write(util_cat.error(false)); - return false; - } - } - } - - if (count != _num_entries) { - util_cat.error() - << "WeakKeyHashMap is invalid: reports " << _num_entries - << " entries, actually has " << count << "\n"; - write(util_cat.error(false)); - return false; - } - - return true; -} - -/** - * Computes an appropriate index number to store the given pointer. - */ -template -INLINE size_t WeakKeyHashMap:: -get_hash(const Key *key) const { - /* - // We want a hash constant 0 < k < 1. This one is suggested by Knuth: - static const double hash_constant = (sqrt(5.0) - 1.0) / 2.0; - double f = ((double)(size_t)key * hash_constant); - f -= floor(f); - return (size_t)floor(f * _table_size); - */ - - return (((size_t)key * (size_t)9973) >> 8) & (_table_size - 1); -} - -/** - * Returns true if element n matches key. - */ -template -INLINE bool WeakKeyHashMap:: -is_element(size_t n, const Key *key) const { - nassertr(has_element(n), false); - return _table[n]._key == key; -} - -/** - * Constructs a new TableEntry at position n, storing the indicated key and - * value. - */ -template -INLINE void WeakKeyHashMap:: -store_new_element(size_t n, const Key *key, const Value &data) { - if (get_exists_array()[n] != 0) { - // There was already an element in this spot. This can happen if it was a - // pointer that had already been deleted. - nassertv(_table[n]._key.was_deleted()); - _table[n].~TableEntry(); - --_num_entries; - } - new(&_table[n]) TableEntry(key, data); - get_exists_array()[n] = true; -} - -/** - * Destructs the TableEntry at position n. - */ -template -INLINE void WeakKeyHashMap:: -clear_element(size_t n) { - _table[n].~TableEntry(); - get_exists_array()[n] = false; -} - -/** - * Returns the beginning of the array of _table_size unsigned chars that are - * the boolean flags for whether each element exists (has been constructed) - * within the table. - */ -template -INLINE unsigned char *WeakKeyHashMap:: -get_exists_array() const { - return (unsigned char *)(_table + _table_size); -} - -/** - * Allocates a brand new table. - */ -template -void WeakKeyHashMap:: -new_table() { - nassertv(_table_size == 0 && _num_entries == 0); - - // Pick a good initial table size. For now, we make it really small. Maybe - // that's the right answer. - _table_size = 4; - - // We allocate enough bytes for _table_size elements of TableEntry, plus - // _table_size more bytes at the end (for the exists array). - size_t alloc_size = _table_size * sizeof(TableEntry) + _table_size; - - _deleted_chain = memory_hook->get_deleted_chain(alloc_size); - _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none()); - memset(get_exists_array(), 0, _table_size); -} - -/** - * Expands the table if it will need it (assuming one more element is about to - * be added). Returns true if the table was modified, false otherwise. - */ -template -INLINE bool WeakKeyHashMap:: -consider_expand_table() { - if (_num_entries >= (_table_size >> 1)) { - // Actually, first, we should see if there are any deleted pointers. - // Clean those up and see how much space we save. - for (size_t i = 0; i < _table_size; ++i) { - if (get_exists_array()[i] != 0 && _table[i]._key.was_deleted()) { - remove_element(i); - } - } - if (_num_entries >= (_table_size >> 1)) { - // Still not enough space. - expand_table(); - } - return true; - } - return false; -} - -/** - * Doubles the size of the existing table. - */ -template -void WeakKeyHashMap:: -expand_table() { - nassertv(_table_size != 0); - - WeakKeyHashMap old_map; - swap(old_map); - - // Double the table size. - size_t old_table_size = old_map._table_size; - _table_size = (old_table_size << 1); - nassertv(_table == nullptr); - - // We allocate enough bytes for _table_size elements of TableEntry, plus - // _table_size more bytes at the end (for the exists array). - size_t alloc_size = _table_size * sizeof(TableEntry) + _table_size; - _deleted_chain = memory_hook->get_deleted_chain(alloc_size); - _table = (TableEntry *)_deleted_chain->allocate(alloc_size, TypeHandle::none()); - unsigned char *exists_array = get_exists_array(); - memset(exists_array, 0, _table_size); - nassertv(_num_entries == 0); - - // Now copy the entries from the old table into the new table. - for (size_t i = 0; i < old_table_size; ++i) { - if (old_map.has_element(i)) { - size_t new_index = get_hash(old_map._table[i]._key.get_orig()); - - while (exists_array[new_index] != 0) { - // Hash conflict; look for a better spot. This has to succeed. - new_index = (new_index + 1) & (_table_size - 1); - } - - // Use C++11 rvalue references to invoke the move constructor, which may - // be more efficient. - new(&_table[new_index]) TableEntry(std::move(old_map._table[i])); - exists_array[new_index] = true; - ++_num_entries; - } - } - - nassertv(validate()); - nassertv(old_map.validate()); - - // Note that since has_element(i) also checks whether the pointer has been - // deleted, we may end up with fewer entries than we started with. Good - // riddance. - nassertv(_num_entries <= old_map._num_entries); -} diff --git a/panda/src/putil/weakKeyHashMap.cxx b/panda/src/putil/weakKeyHashMap.cxx deleted file mode 100644 index 54aecdb527..0000000000 --- a/panda/src/putil/weakKeyHashMap.cxx +++ /dev/null @@ -1,14 +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 weakKeyHashMap.cxx - * @author rdb - * @date 2015-07-13 - */ - -#include "weakKeyHashMap.h" diff --git a/panda/src/putil/weakKeyHashMap.h b/panda/src/putil/weakKeyHashMap.h deleted file mode 100644 index 8e51ae700f..0000000000 --- a/panda/src/putil/weakKeyHashMap.h +++ /dev/null @@ -1,108 +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 weakKeyHashMap.h - * @author rdb - * @date 2015-07-13 - */ - -#ifndef WEAKKEYHASHMAP_H -#define WEAKKEYHASHMAP_H - -#include "pandabase.h" -#include "pvector.h" -#include "config_putil.h" -#include "weakPointerTo.h" - -/** - * This is a variation on WeakKeyHashMap that stores weak pointers as keys, - * and automatically frees up entries from the map when the associated key has - * been deleted. - * - * This is more efficient than using a naive map of WeakPointerTo keys since - * that would incur the cost of constructing a weak reference every time a - * find operation is used. - */ -template -class WeakKeyHashMap { -public: -#ifndef CPPPARSER - INLINE WeakKeyHashMap(); - INLINE ~WeakKeyHashMap(); - - INLINE void swap(WeakKeyHashMap &other); - - int find(const Key *key) const; - int store(const Key *key, const Value &data); - INLINE bool remove(const Key *key); - void clear(); - - INLINE Value &operator [] (const Key *key); - - INLINE size_t get_size() const; - INLINE bool has_element(size_t n) const; - INLINE const Key *get_key(size_t n) const; - INLINE const Value &get_data(size_t n) const; - INLINE Value &modify_data(size_t n); - INLINE void set_data(size_t n, const Value &data); - INLINE void set_data(size_t n, Value &&data); - void remove_element(size_t n); - - INLINE size_t get_num_entries() const; - INLINE bool is_empty() const; - - void output(std::ostream &out) const; - void write(std::ostream &out) const; - bool validate() const; - -private: - INLINE size_t get_hash(const Key *key) const; - - INLINE bool is_element(size_t n, const Key *key) const; - INLINE void store_new_element(size_t n, const Key *key, const Value &data); - INLINE void clear_element(size_t n); - INLINE unsigned char *get_exists_array() const; - - void new_table(); - INLINE bool consider_expand_table(); - void expand_table(); - - class TableEntry { - public: - INLINE TableEntry(const Key *key, const Value &data) : - _key(key), - _data(data) {} - INLINE TableEntry(const TableEntry ©) : - _key(copy._key), - _data(copy._data) {} - INLINE TableEntry(TableEntry &&from) noexcept : - _key(std::move(from._key)), - _data(std::move(from._data)) {} - - WCPT(Key) _key; - Value _data; - }; - - TableEntry *_table; - DeletedBufferChain *_deleted_chain; - size_t _table_size; - size_t _num_entries; -#endif // CPPPARSER -}; - -template -inline std::ostream &operator << (std::ostream &out, const WeakKeyHashMap &shm) { - shm.output(out); - return out; -} - -#ifndef CPPPARSER -#include "weakKeyHashMap.I" -#endif // CPPPARSER - -#endif From 31c532387e531085ed6b4b5a983db3b07e1dfa7c Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Dec 2018 21:36:16 +0100 Subject: [PATCH 388/424] pgraph: remove unused weakKeyHashMap.h import --- panda/src/pgraph/stateMunger.h | 1 - 1 file changed, 1 deletion(-) diff --git a/panda/src/pgraph/stateMunger.h b/panda/src/pgraph/stateMunger.h index c7fa6ef862..9fe71ab63e 100644 --- a/panda/src/pgraph/stateMunger.h +++ b/panda/src/pgraph/stateMunger.h @@ -17,7 +17,6 @@ #include "pandabase.h" #include "geomMunger.h" #include "renderState.h" -#include "weakKeyHashMap.h" /** * This is just a simple derivative of GeomMunger that adds the ability to From f8f0180ee7402631ce08372f057f0f8e2a501095 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 28 Dec 2018 23:27:24 +0100 Subject: [PATCH 389/424] doc: add release notes for Panda 1.10.0 [skip ci] --- doc/ReleaseNotes | 148 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/doc/ReleaseNotes b/doc/ReleaseNotes index 4a8a13ee64..062f129ebd 100644 --- a/doc/ReleaseNotes +++ b/doc/ReleaseNotes @@ -1,3 +1,151 @@ +------------------------ RELEASE 1.10.0 ----------------------- + +This is a major release with significant changes. Please review the +changes when upgrading. The list below is by no means exhaustive, but +should contain the most important changes. + +General +* Experimental ability to build for Android +* New input framework to natively support gamepads, joysticks, etc. +* Multi-threaded render pipeline is a lot more stable now +* New setuptools-based deployment pipeline +* Improvements to mouselook smoothness +* Cache is now at $XDG_CACHE_HOME/panda3d (~/.cache/panda3d), not ~/.panda3d +* Addition of unit test suite +* Many improvements to thread safety +* Many performance improvements +* Tons of bugfixes +* Big style cleanup of C++ source code + +Python API +* Complete support for Python 3 +* Support for coroutines and async/await +* Property interfaces have been added for many settings +* More flexible handling for keyboard arguments in C++ APIs +* Python bindings are completely separated out of the C++ libraries. +* Interrogate binding generator has many improvements. +* Use of pandac.PandaModules is discouraged, use panda3d.core et al +* Use of libRocket is discouraged due to lack of Python 3 support +* Tasks are now sorted in addition order when lacking a sort value +* Fixes iris/fade transitions for extreme aspect ratios +* WeakNodePath is now exposed to Python +* WindowProperties.size(x, y) deprecated; use WindowProperties(size=(x, y)) +* Calling bare run() is deprecated, use base.run() instead +* downcastTo*() methods have been removed, they were already no-ops + +Rendering +* Add new shader-based terrain rendering method (ShaderTerrainMesh) +* The default ColorAttrib mode is now T_vertex +* The ColorAttrib T_off mode now properly disables vertex colors entirely +* Make handling of color attributes more consistent between renderers +* Ability to create an OpenGL core profile context; set "gl-version 3 2" +* Experimental support for reverse-Z rendering for best depth precision +* sRGB framebuffers supported more widely +* Support for infinite near/far clip in lens +* Add some PBR material parameters to material class +* Addition of more built-in GLSL shader inputs; see manual. +* Add p3d_FragData[] GLSL output for MRT in GLSL 1.30 +* Add flag enabling vertex shader control over point size +* Support signed ints and double-precision floats in vertex data with GLSL +* Support unsigned 11/10/10-bit floating-point textures and vertex data +* Support for SSBOs via ShaderBuffer class +* Support OpenGL FBO buffers without any attachments +* Support passing uint variables to GLSL shader +* Allow rendering objects with empty vertex data (for vertex pulling) +* Add LogicOpAttrib, for supporting logical operator blending +* Improvements to OpenGL ES support +* Support for geometry with adjacency information +* Change default alpha blending to improve blending rendered result +* New method for obtaining native OpenGL texture object +* Support windowless offscreen rendering on macOS +* Panda resets OpenGL state better before and after draw callbacks +* OpenGL renderer better supports debugging tools like apitrace +* Support fixed-depth billboards, useful for 2D tags that don't change size + +Shader generator +* Significant performance improvements +* Support for point light shadows +* Hardware skinning support +* Changes to match fixed-function pipeline better +* Fixes for normal vector normalization +* Support multiple normal maps (uses Reoriented Normal Mapping) +* Tracks modifications to materials and texture stages automatically + +Lighting +* Allow specifying light color based on color temperature +* Setting specular color of a light separately is deprecated +* New GLSL inputs to make implementing lighting in shaders much easier +* Add representation for sphere light and rectangle light +* Efficiency improvements for passing light information to shader +* Interocular distance for shadow cameras now always defaults to 0 +* Add low-level lighting module from RenderPipeline + +Textures +* Support cube map arrays +* Support buffer textures +* Many more texture formats supported +* BC4 and BC5 compression modes supported +* Proper depth textures supported in DirectX 9 renderer +* set_ram_image(_as) directly supports buffer protocol +* TexturePeeker supports more formats and component types + +Text +* Dramatic improvements to text rendering performance +* Support for HarfBuzz for higher-quality text shaping and kerning +* Support for right-to-left text +* Support for signed-distance-field rendering in egg-mkfont + +Audio/video +* The default unit for audio is now 1 meter for each Panda unit. +* Native .flac loader +* Support videos with alpha channel in ffmpeg +* OpenAL stability improvements, especially on macOS +* Support loading .opus files with libopusfile +* Fix various memory leaks + +Physics / collisions +* CollisionTube is renamed to CollisionCapsule. +* Box-box collision test is improved to work well with the Pusher +* More box tests for collision system: box-into-plane, box-into-poly +* Capsule (tube) can be used as "from" shape into plane, sphere, capsule, box +* Bullet objects are serializable to .bam files. +* Bullet bindings are now thread safe. +* Bullet debug drawer is more efficient; no longer inherits GeomNode. +* Various fixes to bullet vehicle wheel synchronization +* PhysX bindings are deprecated. + +Pipeline / loading +* Support for Assimp library to load a broad variety of model formats +* Ability to specify min-lod, max-lod, lod-bias in .egg file +* Egg file materials support PBR-style material parameterization +* Support loading more DDS files, including DX10-style ones +* Add support for OpenEXR and HDR textures +* Support line/point thickness in bam2egg +* bam2egg no longer inserts a vestigial ModelNode at the top +* bam2egg supports depth test, offset, cull bin attributes +* Accept a .gz file wherever a .pz file is accepted +* egg-palettize supports mirror and border-color wrap modes +* More robust checks against memory corruptions when loading bad .bam files +* Support for Maya 2017 and 2018 +* Support preprocessing GLSL shaders created with Shader.make + +Build +* We now require using MSVC 2015 or 2017 to compile on Windows. +* At least GCC 4.8 is now required. +* With GCC/clang, enabling C++11 is now required. +* Allow building with more recent ffmpeg versions +* Support for old FFMpeg versions (before 1.1) dropped. +* The ppremake build system has been removed. +* Support for OpenSSL versions before 0.9.7 has been dropped. + +C++ +* Use of NULL is replaced with nullptr +* WeakPointerTo now requires use of lock() method for thread safety +* Mutex et al now satisfy C++11 Lockable constraints +* Panda headers no longer contain `using namespace std;` +* PN_int32 et al have been removed, use stdint.h types instead +* The need to link with pystub and add Python include dirs is removed. + ------------------------ RELEASE 1.9.4 ------------------------ One of the bugfixes in the last 1.9.3 release introduced a regression, From bc2450441d2bb421fafaea7077eab2c75555d561 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 28 Dec 2018 20:23:01 -0700 Subject: [PATCH 390/424] device: Fix some includes --- panda/src/device/linuxInputDeviceManager.cxx | 2 +- panda/src/device/trackerNode.cxx | 2 ++ panda/src/device/winInputDeviceManager.h | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index ce7ce558e2..45a83bfdb3 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -11,7 +11,7 @@ * @date 2018-01-25 */ -#include "inputDeviceManager.h" +#include "linuxInputDeviceManager.h" #include "throw_event.h" #ifdef PHAVE_LINUX_INPUT_H diff --git a/panda/src/device/trackerNode.cxx b/panda/src/device/trackerNode.cxx index 697ccfd0f3..f97849e754 100644 --- a/panda/src/device/trackerNode.cxx +++ b/panda/src/device/trackerNode.cxx @@ -12,7 +12,9 @@ */ #include "trackerNode.h" + #include "config_device.h" +#include "clientTrackerDevice.h" #include "dataNodeTransmit.h" TypeHandle TrackerNode::_type_handle; diff --git a/panda/src/device/winInputDeviceManager.h b/panda/src/device/winInputDeviceManager.h index 3f0e829e38..6c3fefd8c1 100644 --- a/panda/src/device/winInputDeviceManager.h +++ b/panda/src/device/winInputDeviceManager.h @@ -18,7 +18,7 @@ #if defined(_WIN32) && !defined(CPPPARSER) -#include "xinputDevice.h" +#include "xInputDevice.h" #include #include From 5d74411c37d95b241459d7e7a23f3ebd31068347 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Fri, 28 Dec 2018 20:23:38 -0700 Subject: [PATCH 391/424] display: Fix export/import macro for MouseAndKeyboard It was moved here from device, so we should not use EXPCL_PANDA_DEVICE anymore. --- panda/src/display/mouseAndKeyboard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/display/mouseAndKeyboard.h b/panda/src/display/mouseAndKeyboard.h index 66cad13dc0..035822a6ff 100644 --- a/panda/src/display/mouseAndKeyboard.h +++ b/panda/src/display/mouseAndKeyboard.h @@ -38,7 +38,7 @@ * the system, you must attach an EventThrower to the MouseAndKeyboard object; * otherwise, the events will be discarded. */ -class EXPCL_PANDA_DEVICE MouseAndKeyboard : public DataNode { +class EXPCL_PANDA_DISPLAY MouseAndKeyboard : public DataNode { PUBLISHED: explicit MouseAndKeyboard(GraphicsWindow *window, int device, const std::string &name); void set_source(GraphicsWindow *window, int device); From 952031455c06eb10593d729a418d631f23ffb726 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sat, 29 Dec 2018 02:28:47 -0700 Subject: [PATCH 392/424] express: Remove stray reference to DConfig --- panda/src/express/config_express.N | 2 -- 1 file changed, 2 deletions(-) diff --git a/panda/src/express/config_express.N b/panda/src/express/config_express.N index e68b43fbfd..abc2d79e63 100644 --- a/panda/src/express/config_express.N +++ b/panda/src/express/config_express.N @@ -1,5 +1,3 @@ -forcetype DConfig - forcetype PTA_uchar forcetype CPTA_uchar forcetype PTA_float From 818fdbd232f3c53af04ec3d8c3974c0b05a68a78 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Sun, 30 Dec 2018 04:20:50 -0700 Subject: [PATCH 393/424] interrogate: Tidy up hash_string function The main motivation behind this change is to get rid of a signed integer overflow that sometimes happens in the prime multiplication step, which is (per the C++ spec) undefined behavior. However, it's probably for the best to use only unsigned int when the function is clearly trying to avoid negative values. Not that I suspect it matters much, but I have also heavily tested that the behavior of the function is unchanged (at least on PC hardware - signed integer overflow doesn't behave portably) although it may now be slightly faster due to the fact that I have removed the floating-point math. --- dtool/src/interrogate/interrogateBuilder.cxx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/dtool/src/interrogate/interrogateBuilder.cxx b/dtool/src/interrogate/interrogateBuilder.cxx index da0e2a5318..7473d085d3 100644 --- a/dtool/src/interrogate/interrogateBuilder.cxx +++ b/dtool/src/interrogate/interrogateBuilder.cxx @@ -656,13 +656,13 @@ get_preferred_name(CPPType *type) { */ string InterrogateBuilder:: hash_string(const string &name, int shift_offset) { - int hash = 0; + unsigned int hash = 0; - int shift = 0; + unsigned int shift = 0; string::const_iterator ni; for (ni = name.begin(); ni != name.end(); ++ni) { - int c = (int)(unsigned char)(*ni); - int shifted_c = (c << shift) & 0xffffff; + unsigned int c = (unsigned char)*ni; + unsigned int shifted_c = (c << shift) & 0xffffff; if (shift > 16) { // We actually want a circular shift, not an arithmetic shift. shifted_c |= ((c >> (24 - shift)) & 0xff) ; @@ -675,10 +675,9 @@ hash_string(const string &name, int shift_offset) { // bits back at the bottom, to scramble up the bits a bit. This helps // reduce hash conflicts from names that are similar to each other, by // separating adjacent hash codes. - int prime = 4999; - int low_order = (hash * prime) & 0xffffff; - int high_order = (int)((double)hash * (double)prime / (double)(1 << 24)); - hash = low_order ^ high_order; + const unsigned int prime = 4999; + unsigned long long product = (unsigned long long)hash * prime; + hash = (product ^ (product >> 24)) & 0xffffff; // Also add in the additional_number, times some prime factor. hash = (hash // + additional_number * 1657) & 0xffffff; @@ -690,10 +689,9 @@ hash_string(const string &name, int shift_offset) { // deal, since we have to resolve hash conflicts anyway. string result; - int extract_h = hash; for (int i = 0; i < 4; i++) { - int value = (extract_h & 0x3f); - extract_h >>= 6; + unsigned int value = (hash & 0x3f); + hash >>= 6; if (value < 26) { result += (char)('A' + value); From 0a43008313424e222e335f910aee58bbe1aa8081 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 16:42:50 +0100 Subject: [PATCH 394/424] py_panda: avoid duplicate symbol issues with LINK_ALL_STATIC Fixes #478 --- dtool/src/interrogatedb/py_compat.h | 10 +++- dtool/src/interrogatedb/py_panda.cxx | 3 +- dtool/src/interrogatedb/py_panda.h | 68 +++++++++++++-------------- dtool/src/interrogatedb/py_wrappers.h | 12 ++--- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/dtool/src/interrogatedb/py_compat.h b/dtool/src/interrogatedb/py_compat.h index 4872faedc7..11771d1948 100644 --- a/dtool/src/interrogatedb/py_compat.h +++ b/dtool/src/interrogatedb/py_compat.h @@ -31,6 +31,14 @@ #include +#ifndef LINK_ALL_STATIC +# define EXPCL_PYPANDA +#elif defined(__GNUC__) +# define EXPCL_PYPANDA __attribute__((weak)) +#else +# define EXPCL_PYPANDA extern inline +#endif + /* Python 2.4 */ // 2.4 macros which aren't available in 2.3 @@ -99,7 +107,7 @@ typedef int Py_ssize_t; // PyInt_FromSize_t automatically picks the right type. # define PyLongOrInt_AS_LONG PyInt_AsLong -size_t PyLongOrInt_AsSize_t(PyObject *); +EXPCL_PYPANDA size_t PyLongOrInt_AsSize_t(PyObject *); #endif // Which character to use in PyArg_ParseTuple et al for a byte string. diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index 5c984cec82..7c2bef1ce7 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/dtool/src/interrogatedb/py_panda.cxx @@ -665,7 +665,8 @@ PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args) { // We do expose a dictionay for dtool classes .. this should be removed at // some point.. -PyObject *Dtool_AddToDictionary(PyObject *self1, PyObject *args) { +EXPCL_PYPANDA PyObject * +Dtool_AddToDictionary(PyObject *self1, PyObject *args) { PyObject *self; PyObject *subject; PyObject *key; diff --git a/dtool/src/interrogatedb/py_panda.h b/dtool/src/interrogatedb/py_panda.h index ec2765e77a..353af53608 100644 --- a/dtool/src/interrogatedb/py_panda.h +++ b/dtool/src/interrogatedb/py_panda.h @@ -179,19 +179,19 @@ static void Dtool_FreeInstance_##CLASS_NAME(PyObject *self) {\ typedef std::map Dtool_TypeMap; -Dtool_TypeMap *Dtool_GetGlobalTypeMap(); +EXPCL_PYPANDA Dtool_TypeMap *Dtool_GetGlobalTypeMap(); /** */ -void DTOOL_Call_ExtractThisPointerForType(PyObject *self, Dtool_PyTypedObject *classdef, void **answer); +EXPCL_PYPANDA void DTOOL_Call_ExtractThisPointerForType(PyObject *self, Dtool_PyTypedObject *classdef, void **answer); -void *DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef, int param, const std::string &function_name, bool const_ok, bool report_errors); +EXPCL_PYPANDA void *DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef, int param, const std::string &function_name, bool const_ok, bool report_errors); -bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef, void **answer); +EXPCL_PYPANDA bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef, void **answer); -bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef, - void **answer, const char *method_name); +EXPCL_PYPANDA bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef, + void **answer, const char *method_name); template INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into); template INLINE bool DtoolInstance_GetPointer(PyObject *self, T *&into, Dtool_PyTypedObject &classdef); @@ -201,7 +201,7 @@ INLINE int DtoolInstance_ComparePointers(PyObject *v1, PyObject *v2); INLINE PyObject *DtoolInstance_RichComparePointers(PyObject *v1, PyObject *v2, int op); // Functions related to error reporting. -bool _Dtool_CheckErrorOccurred(); +EXPCL_PYPANDA bool _Dtool_CheckErrorOccurred(); #ifdef NDEBUG #define Dtool_CheckErrorOccurred() (UNLIKELY(_PyErr_OCCURRED() != nullptr)) @@ -209,12 +209,12 @@ bool _Dtool_CheckErrorOccurred(); #define Dtool_CheckErrorOccurred() (UNLIKELY(_Dtool_CheckErrorOccurred())) #endif -PyObject *Dtool_Raise_AssertionError(); -PyObject *Dtool_Raise_TypeError(const char *message); -PyObject *Dtool_Raise_ArgTypeError(PyObject *obj, int param, const char *function_name, const char *type_name); -PyObject *Dtool_Raise_AttributeError(PyObject *obj, const char *attribute); +EXPCL_PYPANDA PyObject *Dtool_Raise_AssertionError(); +EXPCL_PYPANDA PyObject *Dtool_Raise_TypeError(const char *message); +EXPCL_PYPANDA PyObject *Dtool_Raise_ArgTypeError(PyObject *obj, int param, const char *function_name, const char *type_name); +EXPCL_PYPANDA PyObject *Dtool_Raise_AttributeError(PyObject *obj, const char *attribute); -PyObject *_Dtool_Raise_BadArgumentsError(); +EXPCL_PYPANDA PyObject *_Dtool_Raise_BadArgumentsError(); #ifdef NDEBUG // Define it to a function that just prints a generic message. #define Dtool_Raise_BadArgumentsError(x) _Dtool_Raise_BadArgumentsError() @@ -226,9 +226,9 @@ PyObject *_Dtool_Raise_BadArgumentsError(); // These functions are similar to Dtool_WrapValue, except that they also // contain code for checking assertions and exceptions when compiling with // NDEBUG mode on. -PyObject *_Dtool_Return_None(); -PyObject *Dtool_Return_Bool(bool value); -PyObject *_Dtool_Return(PyObject *value); +EXPCL_PYPANDA PyObject *_Dtool_Return_None(); +EXPCL_PYPANDA PyObject *Dtool_Return_Bool(bool value); +EXPCL_PYPANDA PyObject *_Dtool_Return(PyObject *value); #ifdef NDEBUG #define Dtool_Return_None() (LIKELY(_PyErr_OCCURRED() == nullptr) ? (Py_INCREF(Py_None), Py_None) : nullptr) @@ -241,19 +241,19 @@ PyObject *_Dtool_Return(PyObject *value); /** * Wrapper around Python 3.4's enum library, which does not have a C API. */ -PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, - const char *module = nullptr); +EXPCL_PYPANDA PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, + const char *module = nullptr); INLINE long Dtool_EnumValue_AsLong(PyObject *value); /** */ -PyObject *DTool_CreatePyInstanceTyped(void *local_this_in, Dtool_PyTypedObject &known_class_type, bool memory_rules, bool is_const, int RunTimeType); +EXPCL_PYPANDA PyObject *DTool_CreatePyInstanceTyped(void *local_this_in, Dtool_PyTypedObject &known_class_type, bool memory_rules, bool is_const, int RunTimeType); // DTool_CreatePyInstance .. wrapper function to finalize the existance of a // general dtool py instance.. -PyObject *DTool_CreatePyInstance(void *local_this, Dtool_PyTypedObject &in_classdef, bool memory_rules, bool is_const); +EXPCL_PYPANDA PyObject *DTool_CreatePyInstance(void *local_this, Dtool_PyTypedObject &in_classdef, bool memory_rules, bool is_const); // These template methods allow use when the Dtool_PyTypedObject is not known. // They require a get_class_type() to be defined for the class. @@ -320,26 +320,26 @@ struct LibraryDef { }; #if PY_MAJOR_VERSION >= 3 -PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], PyModuleDef *module_def); +EXPCL_PYPANDA PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], PyModuleDef *module_def); #else -PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], const char *modulename); +EXPCL_PYPANDA PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], const char *modulename); #endif // HACK.... Be carefull Dtool_BorrowThisReference This function can be used to // grab the "THIS" pointer from an object and use it Required to support fom // historical inharatence in the for of "is this instance of".. -PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args); +EXPCL_PYPANDA PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args); #define DTOOL_PyObject_HashPointer DtoolInstance_HashPointer #define DTOOL_PyObject_ComparePointers DtoolInstance_ComparePointers -PyObject * +EXPCL_PYPANDA PyObject * copy_from_make_copy(PyObject *self, PyObject *noargs); -PyObject * +EXPCL_PYPANDA PyObject * copy_from_copy_constructor(PyObject *self, PyObject *noargs); -PyObject * +EXPCL_PYPANDA PyObject * map_deepcopy_to_copy(PyObject *self, PyObject *args); /** @@ -348,14 +348,14 @@ map_deepcopy_to_copy(PyObject *self, PyObject *args); */ ALWAYS_INLINE bool Dtool_CheckNoArgs(PyObject *args); ALWAYS_INLINE bool Dtool_CheckNoArgs(PyObject *args, PyObject *kwds); -bool Dtool_ExtractArg(PyObject **result, PyObject *args, - PyObject *kwds, const char *keyword); -bool Dtool_ExtractArg(PyObject **result, PyObject *args, - PyObject *kwds); -bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, - PyObject *kwds, const char *keyword); -bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, - PyObject *kwds); +EXPCL_PYPANDA bool Dtool_ExtractArg(PyObject **result, PyObject *args, + PyObject *kwds, const char *keyword); +EXPCL_PYPANDA bool Dtool_ExtractArg(PyObject **result, PyObject *args, + PyObject *kwds); +EXPCL_PYPANDA bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, + PyObject *kwds, const char *keyword); +EXPCL_PYPANDA bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, + PyObject *kwds); /** * These functions convert a C++ value into the corresponding Python object. @@ -390,7 +390,7 @@ ALWAYS_INLINE PyObject *Dtool_WrapValue(Py_buffer *value); template ALWAYS_INLINE PyObject *Dtool_WrapValue(const std::pair &value); -Dtool_PyTypedObject *Dtool_GetSuperBase(); +EXPCL_PYPANDA Dtool_PyTypedObject *Dtool_GetSuperBase(); #include "py_panda.I" diff --git a/dtool/src/interrogatedb/py_wrappers.h b/dtool/src/interrogatedb/py_wrappers.h index 96375d3598..eb5658cb6b 100644 --- a/dtool/src/interrogatedb/py_wrappers.h +++ b/dtool/src/interrogatedb/py_wrappers.h @@ -49,12 +49,12 @@ struct Dtool_GeneratorWrapper { iternextfunc _iternext_func; }; -Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name); -Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, const char *name); -Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name); -Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char *name); -PyObject *Dtool_NewGenerator(PyObject *self, iternextfunc func); -PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset); +EXPCL_PYPANDA Dtool_SequenceWrapper *Dtool_NewSequenceWrapper(PyObject *self, const char *name); +EXPCL_PYPANDA Dtool_MutableSequenceWrapper *Dtool_NewMutableSequenceWrapper(PyObject *self, const char *name); +EXPCL_PYPANDA Dtool_MappingWrapper *Dtool_NewMappingWrapper(PyObject *self, const char *name); +EXPCL_PYPANDA Dtool_MappingWrapper *Dtool_NewMutableMappingWrapper(PyObject *self, const char *name); +EXPCL_PYPANDA PyObject *Dtool_NewGenerator(PyObject *self, iternextfunc func); +EXPCL_PYPANDA PyObject *Dtool_NewStaticProperty(PyTypeObject *obj, const PyGetSetDef *getset); #endif // HAVE_PYTHON From cedd4172c4e517eb40d7d95055056477663bf92e Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 16:45:27 +0100 Subject: [PATCH 395/424] makepanda: build Python libraries statically with --static Fixes #477 --- makepanda/makepandacore.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index f84ac24316..8790538395 100644 --- a/makepanda/makepandacore.py +++ b/makepanda/makepandacore.py @@ -3362,8 +3362,13 @@ def CalcLocation(fn, ipath): def FindLocation(fn, ipath, pyabi=None): - if (GetLinkAllStatic() and fn.endswith(".dll")): - fn = fn[:-4] + ".lib" + if GetLinkAllStatic(): + if fn.endswith(".dll"): + fn = fn[:-4] + ".lib" + elif fn.endswith(".pyd"): + fn = "libpy.panda3d." \ + + os.path.splitext(fn[:-4] + GetExtensionSuffix())[0] + ".lib" + loc = CalcLocation(fn, ipath) base, ext = os.path.splitext(fn) From 73083127bf546ad7169390c2cf932dbbe915a189 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 16:59:32 +0100 Subject: [PATCH 396/424] x11display: bypass compositor when switching to fullscreen mode --- panda/src/x11display/x11GraphicsPipe.cxx | 1 + panda/src/x11display/x11GraphicsPipe.h | 1 + panda/src/x11display/x11GraphicsWindow.cxx | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/panda/src/x11display/x11GraphicsPipe.cxx b/panda/src/x11display/x11GraphicsPipe.cxx index 479b3fde0c..e5e8da18c8 100644 --- a/panda/src/x11display/x11GraphicsPipe.cxx +++ b/panda/src/x11display/x11GraphicsPipe.cxx @@ -241,6 +241,7 @@ x11GraphicsPipe(const std::string &display) : _net_wm_state_below = XInternAtom(_display, "_NET_WM_STATE_BELOW", false); _net_wm_state_add = XInternAtom(_display, "_NET_WM_STATE_ADD", false); _net_wm_state_remove = XInternAtom(_display, "_NET_WM_STATE_REMOVE", false); + _net_wm_bypass_compositor = XInternAtom(_display, "_NET_WM_BYPASS_COMPOSITOR", false); } /** diff --git a/panda/src/x11display/x11GraphicsPipe.h b/panda/src/x11display/x11GraphicsPipe.h index c13b192627..3b8802e6e2 100644 --- a/panda/src/x11display/x11GraphicsPipe.h +++ b/panda/src/x11display/x11GraphicsPipe.h @@ -80,6 +80,7 @@ public: Atom _net_wm_state_below; Atom _net_wm_state_add; Atom _net_wm_state_remove; + Atom _net_wm_bypass_compositor; // Extension functions. typedef int (*pfn_XcursorGetDefaultSize)(X11_Display *); diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index be28a6254f..df038a6dad 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -1272,6 +1272,14 @@ set_wm_properties(const WindowProperties &properties, bool already_mapped) { XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + // Disable compositing effects in fullscreen mode. + if (properties.has_fullscreen()) { + int32_t compositor = properties.get_fullscreen() ? 1 : 0; + XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_bypass_compositor, + XA_CARDINAL, 32, PropModeReplace, + (unsigned char *)&compositor, 1); + } + XChangeProperty(_display, _xwindow, x11_pipe->_net_wm_window_type, XA_ATOM, 32, PropModeReplace, (unsigned char *)type_data, next_type_data); From 62781c154b97ea57a2b39c98d4ca0d28e29ed404 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 20:09:57 +0100 Subject: [PATCH 397/424] makepanda: fix instructions to find Opus thirdparty pkg on Windows --- makepanda/makepanda.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index f4f3da2357..40febe3cd0 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -808,9 +808,12 @@ if (COMPILER == "MSVC"): path = GetThirdpartyDir() + "vorbis/lib/{0}.lib".format(lib) LibName("VORBIS", path) if (PkgSkip("OPUS")==0): - LibName("OPUS", GetThirdpartyDir() + "opus/lib/libogg_static.lib") - LibName("OPUS", GetThirdpartyDir() + "opus/lib/libopus_static.lib") - LibName("OPUS", GetThirdpartyDir() + "opus/lib/libopusfile_static.lib") + IncDirectory("OPUS", GetThirdpartyDir() + "opus/include/opus") + for lib in ('ogg', 'opus', 'opusfile'): + path = GetThirdpartyDir() + "opus/lib/lib{0}_static.lib".format(lib) + if not os.path.isfile(path): + path = GetThirdpartyDir() + "opus/lib/{0}.lib".format(lib) + LibName("OPUS", path) for pkg in MAYAVERSIONS: if (PkgSkip(pkg)==0): LibName(pkg, '"' + SDK[pkg] + '/lib/Foundation.lib"') From 42405dff2edd11bc4e05058cc5befa817c74ebe9 Mon Sep 17 00:00:00 2001 From: rdb Date: Sun, 30 Dec 2018 20:10:50 +0100 Subject: [PATCH 398/424] pnmimagetypes: fix allocation efficiency in PNG reader Rather than making one allocation per row, it instead makes one allocation for all rows. --- panda/src/pnmimagetypes/pnmFileTypePNG.cxx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/panda/src/pnmimagetypes/pnmFileTypePNG.cxx b/panda/src/pnmimagetypes/pnmFileTypePNG.cxx index 799f4d1dce..7d64faed50 100644 --- a/panda/src/pnmimagetypes/pnmFileTypePNG.cxx +++ b/panda/src/pnmimagetypes/pnmFileTypePNG.cxx @@ -338,11 +338,12 @@ read_data(xel *array, xelval *alpha_data) { // format, mainly because we keep array and alpha data separately, and there // doesn't appear to be good support to get this stuff out row-at-a-time for // interlaced files. - png_bytep *rows = (png_bytep *)PANDA_MALLOC_ARRAY(num_rows * sizeof(png_bytep)); + png_bytep *rows = (png_bytep *)alloca(num_rows * sizeof(png_bytep)); int yi; + png_byte *alloc = (png_byte *)PANDA_MALLOC_ARRAY(row_byte_length * sizeof(png_byte) * num_rows); for (yi = 0; yi < num_rows; yi++) { - rows[yi] = (png_byte *)PANDA_MALLOC_ARRAY(row_byte_length * sizeof(png_byte)); + rows[yi] = alloc + (row_byte_length * sizeof(png_byte)) * yi; } png_read_image(_png, rows); @@ -402,12 +403,10 @@ read_data(xel *array, xelval *alpha_data) { } nassertr(source <= rows[yi] + row_byte_length, yi); - PANDA_FREE_ARRAY(rows[yi]); } - PANDA_FREE_ARRAY(rows); - png_read_end(_png, nullptr); + PANDA_FREE_ARRAY(alloc); return _y_size; } From 4a7266d420622185f4adca335545b247382f6c0a Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 13:09:39 +0100 Subject: [PATCH 399/424] =?UTF-8?q?samples:=20float4=E2=86=92float3=20for?= =?UTF-8?q?=20vtx=5Fnormal=20in=20cartoon=20&=20fireflies=20samples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It seems that Cg's glslv profile causes the w coordinate of the normal column to be random. Fixes #494 Fixes #495 --- samples/cartoon-shader/normalGen.sha | 18 ++++++++---------- samples/fireflies/model.sha | 4 ++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/samples/cartoon-shader/normalGen.sha b/samples/cartoon-shader/normalGen.sha index a3642b0630..53b994b1ae 100644 --- a/samples/cartoon-shader/normalGen.sha +++ b/samples/cartoon-shader/normalGen.sha @@ -3,14 +3,14 @@ //Cg profile arbvp1 arbfp1 void vshader(float4 vtx_position : POSITION, - float4 vtx_normal : NORMAL, - out float4 l_position : POSITION, - out float3 l_color : TEXCOORD0, - uniform float4x4 mat_modelproj, - uniform float4x4 itp_modelview) + float3 vtx_normal : NORMAL, + out float4 l_position : POSITION, + out float3 l_color : TEXCOORD0, + uniform float4x4 mat_modelproj, + uniform float4x4 itp_modelview) { - l_position=mul(mat_modelproj, vtx_position); - l_color=(float3)mul(itp_modelview, vtx_normal); + l_position = mul(mat_modelproj, vtx_position); + l_color = (float3)mul(itp_modelview, float4(vtx_normal, 0)); } void fshader(float3 l_color: TEXCOORD0, @@ -18,8 +18,6 @@ void fshader(float3 l_color: TEXCOORD0, { l_color = normalize(l_color); l_color = l_color/2; - o_color.rgb = l_color + float4(0.5, 0.5, 0.5, 0.5); + o_color.rgb = l_color.rgb + float3(0.5, 0.5, 0.5); o_color.a = 1; } - - diff --git a/samples/fireflies/model.sha b/samples/fireflies/model.sha index f1867ee5da..0c2e7fd920 100644 --- a/samples/fireflies/model.sha +++ b/samples/fireflies/model.sha @@ -4,7 +4,7 @@ void vshader(float4 vtx_position : POSITION, float2 vtx_texcoord0 : TEXCOORD0, - float4 vtx_normal : NORMAL, + float3 vtx_normal : NORMAL, float4 vtx_color : COLOR, out float4 l_position : POSITION, out float2 l_texcoord0 : TEXCOORD0, @@ -16,7 +16,7 @@ void vshader(float4 vtx_position : POSITION, l_position=mul(mat_modelproj, vtx_position); l_texcoord0 = vtx_texcoord0; l_color = vtx_color; - l_normal = (float3)mul(itp_modelview, vtx_normal); + l_normal = (float3)mul(itp_modelview, float4(vtx_normal, 0)); } void fshader(float2 l_texcoord0: TEXCOORD0, From 0cfd86d0f4128296d322293658335c5422501897 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 13:11:33 +0100 Subject: [PATCH 400/424] glgsg: warn if Cg shader uses float4 vtx_normal See #495; Cg's glslv profile does not handle a float4 vtx_normal well and causes garbage to be in the w coordinate. --- panda/src/glstuff/glCgShaderContext_src.cxx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/panda/src/glstuff/glCgShaderContext_src.cxx b/panda/src/glstuff/glCgShaderContext_src.cxx index 649208fb47..6e7fc565cc 100644 --- a/panda/src/glstuff/glCgShaderContext_src.cxx +++ b/panda/src/glstuff/glCgShaderContext_src.cxx @@ -157,6 +157,16 @@ CLP(CgShaderContext)(CLP(GraphicsStateGuardian) *glgsg, Shader *s) : ShaderConte break; case 2: // gl_Normal loc = CA_normal; + if (cgGetParameterColumns(p) == 4) { + // Don't declare vtx_normal with 4 coordinates; it results in it + // reading the w coordinate from random memory. + GLCAT.error() + << "Cg varying " << cgGetParameterName(p); + if (cgGetParameterSemantic(p)) { + GLCAT.error(false) << " : " << cgGetParameterSemantic(p); + } + GLCAT.error(false) << " should be declared as float4, not float3!\n"; + } break; case 3: // gl_Color loc = CA_color; From 8119547e9015ce90ab28c3191ec9a1f4b614f9bb Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 13:12:40 +0100 Subject: [PATCH 401/424] device: add hacky detection of Trust GXT 24 as gamepad for now --- panda/src/device/evdevInputDevice.cxx | 2 ++ panda/src/device/winRawInputDevice.cxx | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index a5c916d8bc..610fb8722a 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -73,6 +73,8 @@ static const struct DeviceMapping { {0x045e, 0x0719, InputDevice::DeviceClass::gamepad, QB_connect_if_nonzero}, // Jess Tech Colour Rumble Pad {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0}, + // Trust GXT 24 + {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, 0}, // 3Dconnexion Space Traveller 3D Mouse {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0}, // 3Dconnexion Space Pilot 3D Mouse diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index 1af01cd4b4..f49a7e0d7f 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -273,6 +273,11 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // Well, it claims to be a gamepad... _device_class = DeviceClass::gamepad; } + //TODO: better solution for this + if (_vendor_id == 0x0079 && _product_id == 0x0006) { + // Trust GXT 24 + _device_class = DeviceClass::gamepad; + } // Mice } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && From 9382e09b7ca29deec9f299544dfcb32828f364b7 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 13:13:45 +0100 Subject: [PATCH 402/424] display: fix returning reference to temporary in shader input This might cause issues when the transform cache is off. --- panda/src/display/graphicsStateGuardian.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index 1b0caeabd0..93a3f36651 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/panda/src/display/graphicsStateGuardian.cxx @@ -1255,7 +1255,8 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name, return &(_scene_setup->get_camera_transform()->get_mat()); } case Shader::SMO_model_to_view: { - return &(_inv_cs_transform->compose(_internal_transform)->get_mat()); + t = _inv_cs_transform->compose(_internal_transform)->get_mat(); + return &t; } case Shader::SMO_model_to_apiview: { return &(_internal_transform->get_mat()); From 83374de0fffee571849127498bf9dd66d2c352e7 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 14:01:26 +0100 Subject: [PATCH 403/424] shader: don't use GENERIC profile to compile Cg shaders Instead, make an educated guess of what the GSG we are going to be compiling this shader for wants to use as profile. --- panda/src/display/config_display.cxx | 9 -------- panda/src/display/config_display.h | 1 - .../glstuff/glGraphicsStateGuardian_src.cxx | 2 ++ panda/src/gobj/config_gobj.cxx | 9 ++++++++ panda/src/gobj/config_gobj.h | 1 + panda/src/gobj/shader.cxx | 21 ++++++++++++++++--- panda/src/gobj/shader.h | 2 ++ 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/panda/src/display/config_display.cxx b/panda/src/display/config_display.cxx index 78c0b17418..965d68f4e3 100644 --- a/panda/src/display/config_display.cxx +++ b/panda/src/display/config_display.cxx @@ -478,15 +478,6 @@ ConfigVariableBool sync_video "cheesy estimate of scene complexity. Some drivers may ignore " "this request.")); -ConfigVariableBool basic_shaders_only -("basic-shaders-only", false, - PRC_DESC("Set this to true if you aren't interested in shader model three " - "and beyond. Setting this flag will cause panda to disable " - "bleeding-edge shader functionality which tends to be unreliable " - "or broken. At some point, when functionality that is currently " - "flaky becomes reliable, we may expand the definition of what " - "constitutes 'basic' shaders.")); - /** * Initializes the library. This must be called at least once before any of * the functions or classes in this library can be used. Normally it will be diff --git a/panda/src/display/config_display.h b/panda/src/display/config_display.h index 0b76e06472..21f1acb510 100644 --- a/panda/src/display/config_display.h +++ b/panda/src/display/config_display.h @@ -108,7 +108,6 @@ extern EXPCL_PANDA_DISPLAY ConfigVariableDouble pixel_zoom; extern EXPCL_PANDA_DISPLAY ConfigVariableColor background_color; extern EXPCL_PANDA_DISPLAY ConfigVariableBool sync_video; -extern EXPCL_PANDA_DISPLAY ConfigVariableBool basic_shaders_only; extern EXPCL_PANDA_DISPLAY void init_libdisplay(); diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx index 1e6d1781cc..6151a2c00d 100644 --- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx +++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx @@ -1684,6 +1684,8 @@ reset() { } } + Shader::set_default_caps(_shader_caps); + } else if (_supports_glsl) { // No, but we do support GLSL... _shader_caps._active_vprofile = (int)CG_PROFILE_GLSLV; diff --git a/panda/src/gobj/config_gobj.cxx b/panda/src/gobj/config_gobj.cxx index 12840f7d04..07075a94a8 100644 --- a/panda/src/gobj/config_gobj.cxx +++ b/panda/src/gobj/config_gobj.cxx @@ -511,6 +511,15 @@ ConfigVariableBool stereo_lens_old_convergence "old, incorrect behavior, this may be set to 'true' to switch " "back to the old calculation.")); +ConfigVariableBool basic_shaders_only +("basic-shaders-only", false, + PRC_DESC("Set this to true if you aren't interested in shader model three " + "and beyond. Setting this flag will cause panda to disable " + "bleeding-edge shader functionality which tends to be unreliable " + "or broken. At some point, when functionality that is currently " + "flaky becomes reliable, we may expand the definition of what " + "constitutes 'basic' shaders.")); + ConfigVariableString cg_glsl_version ("cg-glsl-version", "", PRC_DESC("If this is set, it forces the Cg compiler to generate GLSL " diff --git a/panda/src/gobj/config_gobj.h b/panda/src/gobj/config_gobj.h index 7e769c0f45..90a5673c12 100644 --- a/panda/src/gobj/config_gobj.h +++ b/panda/src/gobj/config_gobj.h @@ -87,6 +87,7 @@ extern EXPCL_PANDA_GOBJ ConfigVariableDouble async_load_delay; extern EXPCL_PANDA_GOBJ ConfigVariableInt lens_geom_segments; extern EXPCL_PANDA_GOBJ ConfigVariableBool stereo_lens_old_convergence; +extern EXPCL_PANDA_GOBJ ConfigVariableBool basic_shaders_only; extern EXPCL_PANDA_GOBJ ConfigVariableString cg_glsl_version; extern EXPCL_PANDA_GOBJ ConfigVariableBool glsl_preprocess; extern EXPCL_PANDA_GOBJ ConfigVariableInt glsl_include_recursion_limit; diff --git a/panda/src/gobj/shader.cxx b/panda/src/gobj/shader.cxx index fde084aa18..d42e6a4734 100644 --- a/panda/src/gobj/shader.cxx +++ b/panda/src/gobj/shader.cxx @@ -1589,6 +1589,15 @@ get_compiled(unsigned int &format, string &binary) const { return !binary.empty(); } +/** + * Called by the graphics back-end to specify the caps with which we will + * likely want to be compiling our shaders. + */ +void Shader:: +set_default_caps(const ShaderCaps &caps) { + _default_caps = caps; +} + #ifdef HAVE_CG /** * @@ -2364,9 +2373,15 @@ Shader(ShaderLanguage lang) : _cg_fprofile = CG_PROFILE_UNKNOWN; _cg_gprofile = CG_PROFILE_UNKNOWN; if (_default_caps._ultimate_vprofile == 0 || _default_caps._ultimate_vprofile == CG_PROFILE_UNKNOWN) { - _default_caps._active_vprofile = CG_PROFILE_GENERIC; - _default_caps._active_fprofile = CG_PROFILE_GENERIC; - _default_caps._active_gprofile = CG_PROFILE_GENERIC; + if (basic_shaders_only) { + _default_caps._active_vprofile = CG_PROFILE_ARBVP1; + _default_caps._active_fprofile = CG_PROFILE_ARBFP1; + _default_caps._active_gprofile = CG_PROFILE_UNKNOWN; + } else { + _default_caps._active_vprofile = CG_PROFILE_UNKNOWN; + _default_caps._active_fprofile = CG_PROFILE_UNKNOWN; + _default_caps._active_gprofile = CG_PROFILE_UNKNOWN; + } _default_caps._ultimate_vprofile = cgGetProfile("glslv"); _default_caps._ultimate_fprofile = cgGetProfile("glslf"); _default_caps._ultimate_gprofile = cgGetProfile("glslg"); diff --git a/panda/src/gobj/shader.h b/panda/src/gobj/shader.h index 35971b243b..940e2d0ccf 100644 --- a/panda/src/gobj/shader.h +++ b/panda/src/gobj/shader.h @@ -527,6 +527,8 @@ public: void set_compiled(unsigned int format, const char *data, size_t length); bool get_compiled(unsigned int &format, std::string &binary) const; + static void set_default_caps(const ShaderCaps &caps); + private: #ifdef HAVE_CG ShaderArgClass cg_parameter_class(CGparameter p); From 4699dfcd5b8334e9ada059f92c7fc7b80a5a49a2 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 14:07:14 +0100 Subject: [PATCH 404/424] ShaderGenerator: fix toggling shadows while shader gen is on --- panda/src/pgraphnodes/lightLensNode.I | 53 +++----------------- panda/src/pgraphnodes/lightLensNode.cxx | 60 ++++++++++++++++++++++- panda/src/pgraphnodes/lightLensNode.h | 8 ++- panda/src/pgraphnodes/shaderGenerator.cxx | 10 +++- 4 files changed, 80 insertions(+), 51 deletions(-) diff --git a/panda/src/pgraphnodes/lightLensNode.I b/panda/src/pgraphnodes/lightLensNode.I index 9c59b6c998..4fd1138b03 100644 --- a/panda/src/pgraphnodes/lightLensNode.I +++ b/panda/src/pgraphnodes/lightLensNode.I @@ -28,51 +28,6 @@ is_shadow_caster() const { return _shadow_caster; } -/** - * Sets the flag indicating whether this light should cast shadows or not. - * This is the variant without buffer size, meaning that the current buffer - * size will be kept (512x512 is the default). Note that enabling shadows will - * require the shader generator to be enabled on the scene. - */ -INLINE void LightLensNode:: -set_shadow_caster(bool caster) { - if (_shadow_caster && !caster) { - clear_shadow_buffers(); - } - _shadow_caster = caster; - set_active(caster); - if (caster) { - setup_shadow_map(); - } -} - -/** - * Sets the flag indicating whether this light should cast shadows or not. - * The xsize and ysize parameters specify the size of the shadow buffer that - * will be set up, the sort parameter specifies the sort. Note that enabling - * shadows will require the shader generator to be enabled on the scene. - */ -INLINE void LightLensNode:: -set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int buffer_sort) { - if ((_shadow_caster && !caster) || buffer_xsize != _sb_size[0] || buffer_ysize != _sb_size[1]) { - clear_shadow_buffers(); - } - _shadow_caster = caster; - _sb_size.set(buffer_xsize, buffer_ysize); - - if (buffer_sort != _sb_sort) { - ShadowBuffers::iterator it; - for(it = _sbuffers.begin(); it != _sbuffers.end(); ++it) { - (*it).second->set_sort(buffer_sort); - } - _sb_sort = buffer_sort; - } - set_active(caster); - if (caster) { - setup_shadow_map(); - } -} - /** * Returns the sort of the shadow buffer to be created for this light source. */ @@ -115,3 +70,11 @@ get_shadow_buffer(GraphicsStateGuardianBase *gsg) { return (*it).second; } } + +/** + * Marks this light as having been used by the auto shader. + */ +INLINE void LightLensNode:: +mark_used_by_auto_shader() const { + _used_by_auto_shader = true; +} diff --git a/panda/src/pgraphnodes/lightLensNode.cxx b/panda/src/pgraphnodes/lightLensNode.cxx index 87f9a5bca8..aaa290e08b 100644 --- a/panda/src/pgraphnodes/lightLensNode.cxx +++ b/panda/src/pgraphnodes/lightLensNode.cxx @@ -19,6 +19,7 @@ #include "renderState.h" #include "cullFaceAttrib.h" #include "colorWriteAttrib.h" +#include "graphicsStateGuardianBase.h" TypeHandle LightLensNode::_type_handle; @@ -29,7 +30,8 @@ LightLensNode:: LightLensNode(const std::string &name, Lens *lens) : Camera(name, lens), _has_specular_color(false), - _attrib_count(0) + _attrib_count(0), + _used_by_auto_shader(false) { set_active(false); _shadow_caster = false; @@ -65,13 +67,67 @@ LightLensNode(const LightLensNode ©) : _sb_size(copy._sb_size), _sb_sort(-10), _has_specular_color(copy._has_specular_color), - _attrib_count(0) + _attrib_count(0), + _used_by_auto_shader(false) { if (_shadow_caster) { setup_shadow_map(); } } +/** + * Sets the flag indicating whether this light should cast shadows or not. + * This is the variant without buffer size, meaning that the current buffer + * size will be kept (512x512 is the default). Note that enabling shadows will + * require the shader generator to be enabled on the scene. + */ +void LightLensNode:: +set_shadow_caster(bool caster) { + if (_shadow_caster && !caster) { + clear_shadow_buffers(); + } + if (_shadow_caster != caster && _used_by_auto_shader) { + // Make sure any shaders using this light are regenerated. + GraphicsStateGuardianBase::mark_rehash_generated_shaders(); + } + _shadow_caster = caster; + set_active(caster); + if (caster) { + setup_shadow_map(); + } +} + +/** + * Sets the flag indicating whether this light should cast shadows or not. + * The xsize and ysize parameters specify the size of the shadow buffer that + * will be set up, the sort parameter specifies the sort. Note that enabling + * shadows will require the shader generator to be enabled on the scene. + */ +void LightLensNode:: +set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int buffer_sort) { + if ((_shadow_caster && !caster) || buffer_xsize != _sb_size[0] || buffer_ysize != _sb_size[1]) { + clear_shadow_buffers(); + } + if (_shadow_caster != caster && _used_by_auto_shader) { + // Make sure any shaders using this light are regenerated. + GraphicsStateGuardianBase::mark_rehash_generated_shaders(); + } + _shadow_caster = caster; + _sb_size.set(buffer_xsize, buffer_ysize); + + if (buffer_sort != _sb_sort) { + ShadowBuffers::iterator it; + for(it = _sbuffers.begin(); it != _sbuffers.end(); ++it) { + (*it).second->set_sort(buffer_sort); + } + _sb_sort = buffer_sort; + } + set_active(caster); + if (caster) { + setup_shadow_map(); + } +} + /** * Clears the shadow buffers, meaning they will be automatically recreated * when the Shader Generator needs them. diff --git a/panda/src/pgraphnodes/lightLensNode.h b/panda/src/pgraphnodes/lightLensNode.h index 4398f02bfd..f045c539b3 100644 --- a/panda/src/pgraphnodes/lightLensNode.h +++ b/panda/src/pgraphnodes/lightLensNode.h @@ -38,8 +38,8 @@ PUBLISHED: INLINE bool has_specular_color() const; INLINE bool is_shadow_caster() const; - INLINE void set_shadow_caster(bool caster); - INLINE void set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int sort = -10); + void set_shadow_caster(bool caster); + void set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int sort = -10); INLINE int get_shadow_buffer_sort() const; @@ -52,6 +52,9 @@ PUBLISHED: MAKE_PROPERTY(shadow_caster, is_shadow_caster); MAKE_PROPERTY(shadow_buffer_size, get_shadow_buffer_size, set_shadow_buffer_size); +public: + INLINE void mark_used_by_auto_shader() const; + protected: LightLensNode(const LightLensNode ©); void clear_shadow_buffers(); @@ -61,6 +64,7 @@ protected: bool _shadow_caster; bool _has_specular_color; int _sb_sort; + mutable bool _used_by_auto_shader = false; PT(Texture) _shadow_map; diff --git a/panda/src/pgraphnodes/shaderGenerator.cxx b/panda/src/pgraphnodes/shaderGenerator.cxx index cf8f0783d4..16a7483478 100644 --- a/panda/src/pgraphnodes/shaderGenerator.cxx +++ b/panda/src/pgraphnodes/shaderGenerator.cxx @@ -294,8 +294,14 @@ analyze_renderstate(ShaderKey &key, const RenderState *rs) { if (node->is_of_type(LightLensNode::get_class_type())) { const LightLensNode *llnode = (const LightLensNode *)node; - if (shader_attrib->auto_shadow_on() && llnode->is_shadow_caster()) { - info._flags |= ShaderKey::LF_has_shadows; + if (shader_attrib->auto_shadow_on()) { + if (llnode->is_shadow_caster()) { + info._flags |= ShaderKey::LF_has_shadows; + } + + // Make sure that the next time the shadows are toggled on this + // light, it triggers a state rehash. + llnode->mark_used_by_auto_shader(); } if (llnode->has_specular_color()) { info._flags |= ShaderKey::LF_has_specular_color; From f3ba1d317c7b9e1b3d424a32f1ae86f2404713a7 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 16:10:56 +0100 Subject: [PATCH 405/424] device: support for Steam Controller on Linux Adds lgrip and rgrip button handles, which are present on Steam Controller but also on the Oculus Touch --- panda/src/device/evdevInputDevice.cxx | 43 ++++++++++++++++++++++-- panda/src/device/evdevInputDevice.h | 4 ++- panda/src/device/linuxJoystickDevice.cxx | 25 +++++++++++--- panda/src/device/linuxJoystickDevice.h | 2 +- panda/src/putil/gamepadButton.cxx | 4 +++ panda/src/putil/gamepadButton.h | 2 ++ 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 610fb8722a..057d9b14ff 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -57,6 +57,9 @@ enum QuirkBits { // ABS_THROTTLE maps to rudder QB_rudder_from_throttle = 16, + + // Special handling for Steam Controller, which has many peculiarities. + QB_steam_controller = 32, }; static const struct DeviceMapping { @@ -71,6 +74,10 @@ static const struct DeviceMapping { {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}, + // Steam Controller (wired) + {0x28de, 0x1102, InputDevice::DeviceClass::unknown, QB_steam_controller}, + // Steam Controller (wireless) + {0x28de, 0x1142, InputDevice::DeviceClass::unknown, QB_steam_controller}, // Jess Tech Colour Rumble Pad {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0}, // Trust GXT 24 @@ -299,6 +306,13 @@ init_device() { ++mapping; } + // The Steam Controller reports as multiple devices, one of which a gamepad. + if (quirks & QB_steam_controller) { + if (test_bit(BTN_GAMEPAD, keys)) { + _device_class = DeviceClass::gamepad; + } + } + // Try to detect which type of device we have here if (_device_class == DeviceClass::unknown) { int device_scores[(size_t)DeviceClass::spatial_mouse] = {0}; @@ -378,7 +392,7 @@ init_device() { for (int i = 0; i <= KEY_MAX; ++i) { if (test_bit(i, keys)) { ButtonState button; - button.handle = map_button(i, _device_class); + button.handle = map_button(i, _device_class, quirks); int button_index = (int)_buttons.size(); if (button.handle == ButtonHandle::none()) { @@ -527,6 +541,18 @@ init_device() { } } break; + case ABS_HAT2X: + if (quirks & QB_steam_controller) { + axis = InputDevice::Axis::right_trigger; + have_analog_triggers = true; + } + break; + case ABS_HAT2Y: + if (quirks & QB_steam_controller) { + axis = InputDevice::Axis::left_trigger; + have_analog_triggers = true; + } + break; } // Check the initial value and ranges. @@ -740,7 +766,7 @@ process_events() { * Static function to map an evdev code to a ButtonHandle. */ ButtonHandle EvdevInputDevice:: -map_button(int code, DeviceClass device_class) { +map_button(int code, DeviceClass device_class, int quirks) { if (code >= 0 && code < 0x80) { // See linux/input.h for the source of this mapping. static const ButtonHandle keyboard_map[] = { @@ -897,7 +923,11 @@ map_button(int code, DeviceClass device_class) { } } else if ((code & 0xfff0) == BTN_JOYSTICK) { - if (device_class == DeviceClass::gamepad) { + if (quirks & QB_steam_controller) { + // BTN_THUMB and BTN_THUMB2 detect touching the touchpads. + return ButtonHandle::none(); + + } else if (device_class == DeviceClass::gamepad) { // Based on "Jess Tech Colour Rumble Pad" static const ButtonHandle mapping[] = { GamepadButton::face_x(), @@ -991,6 +1021,13 @@ map_button(int code, DeviceClass device_class) { case BTN_TRIGGER_HAPPY4: return GamepadButton::dpad_down(); + // The next two are for the Steam Controller's grip buttons. + case BTN_GEAR_DOWN: + return GamepadButton::lgrip(); + + case BTN_GEAR_UP: + return GamepadButton::rgrip(); + default: return ButtonHandle::none(); } diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 53c495ad08..994915e40b 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -64,7 +64,9 @@ private: int _rtrigger_code; public: - static ButtonHandle map_button(int code, DeviceClass device_class = DeviceClass::unknown); + static ButtonHandle map_button(int code, + DeviceClass device_class = DeviceClass::unknown, + int quirks = 0); public: static TypeHandle get_class_type() { diff --git a/panda/src/device/linuxJoystickDevice.cxx b/panda/src/device/linuxJoystickDevice.cxx index 1871a1e948..c964ea8499 100644 --- a/panda/src/device/linuxJoystickDevice.cxx +++ b/panda/src/device/linuxJoystickDevice.cxx @@ -113,6 +113,7 @@ open_device() { ioctl(_fd, JSIOCGNAME(sizeof(name)), name); _name = name; + bool emulate_dpad = true; bool have_analog_triggers = false; // Get the number of axes. @@ -138,6 +139,8 @@ open_device() { _device_class = DeviceClass::gamepad; } else if (handle == GamepadButton::trigger()) { _device_class = DeviceClass::flight_stick; + } else if (handle == GamepadButton::dpad_left()) { + emulate_dpad = false; } else if (handle == GamepadButton::ltrigger()) { _ltrigger_button = i; } else if (handle == GamepadButton::rtrigger()) { @@ -220,7 +223,7 @@ open_device() { break; case ABS_HAT0X: - if (_dpad_left_button == -1) { + if (emulate_dpad) { // Emulate D-Pad or hat switch. _dpad_x_axis = i; _dpad_left_button = (int)_buttons.size(); @@ -236,7 +239,7 @@ open_device() { break; case ABS_HAT0Y: - if (_dpad_up_button == -1) { + if (emulate_dpad) { // Emulate D-Pad. _dpad_y_axis = i; _dpad_up_button = (int)_buttons.size(); @@ -251,6 +254,18 @@ open_device() { } break; + case ABS_HAT2X: + if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::right_trigger; + } + break; + + case ABS_HAT2Y: + if (_device_class == DeviceClass::gamepad) { + axis = InputDevice::Axis::left_trigger; + } + break; + default: if (device_cat.is_debug()) { device_cat.debug() << "Unmapped /dev/input/js" << _index @@ -278,7 +293,7 @@ open_device() { if (_ltrigger_button >= 0 && _rtrigger_button >= 0 && !have_analog_triggers) { // Emulate analog triggers. - _ltrigger_control = (int)_axes.size(); + _ltrigger_axis = (int)_axes.size(); add_axis(Axis::left_trigger, 0, 1, false); add_axis(Axis::right_trigger, 0, 1, false); } else { @@ -398,9 +413,9 @@ process_events() { if (events[i].type & JS_EVENT_BUTTON) { if (index == _ltrigger_button) { - axis_changed(_ltrigger_control, events[i].value); + axis_changed(_ltrigger_axis, events[i].value); } else if (index == _rtrigger_button) { - axis_changed(_ltrigger_control + 1, events[i].value); + axis_changed(_ltrigger_axis + 1, events[i].value); } button_changed(index, (events[i].value != 0)); diff --git a/panda/src/device/linuxJoystickDevice.h b/panda/src/device/linuxJoystickDevice.h index af0a74e3e6..61d05cd3e8 100644 --- a/panda/src/device/linuxJoystickDevice.h +++ b/panda/src/device/linuxJoystickDevice.h @@ -50,7 +50,7 @@ private: int _dpad_up_button; // This is used for axis emulation. - int _ltrigger_control; + int _ltrigger_axis; int _ltrigger_button; int _rtrigger_button; diff --git a/panda/src/putil/gamepadButton.cxx b/panda/src/putil/gamepadButton.cxx index 2924f0bf90..4e3c8be22c 100644 --- a/panda/src/putil/gamepadButton.cxx +++ b/panda/src/putil/gamepadButton.cxx @@ -24,6 +24,8 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(lshoulder) DEFINE_GAMEPAD_BUTTON_HANDLE(rshoulder) DEFINE_GAMEPAD_BUTTON_HANDLE(ltrigger) DEFINE_GAMEPAD_BUTTON_HANDLE(rtrigger) +DEFINE_GAMEPAD_BUTTON_HANDLE(lgrip) +DEFINE_GAMEPAD_BUTTON_HANDLE(rgrip) DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_left) DEFINE_GAMEPAD_BUTTON_HANDLE(dpad_right) @@ -87,6 +89,8 @@ init_gamepad_buttons() { ButtonRegistry::ptr()->register_button(_rshoulder, "rshoulder"); ButtonRegistry::ptr()->register_button(_ltrigger, "ltrigger"); ButtonRegistry::ptr()->register_button(_rtrigger, "rtrigger"); + ButtonRegistry::ptr()->register_button(_lgrip, "lgrip"); + ButtonRegistry::ptr()->register_button(_rgrip, "rgrip"); ButtonRegistry::ptr()->register_button(_dpad_left, "dpad_left"); ButtonRegistry::ptr()->register_button(_dpad_right, "dpad_right"); diff --git a/panda/src/putil/gamepadButton.h b/panda/src/putil/gamepadButton.h index 774ee8ef2f..ff89c7e294 100644 --- a/panda/src/putil/gamepadButton.h +++ b/panda/src/putil/gamepadButton.h @@ -30,6 +30,8 @@ PUBLISHED: static ButtonHandle rshoulder(); static ButtonHandle ltrigger(); static ButtonHandle rtrigger(); + static ButtonHandle lgrip(); + static ButtonHandle rgrip(); static ButtonHandle dpad_left(); static ButtonHandle dpad_right(); From 6559932c7b7c6e02af4ae57e37c634c79d73f526 Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 16:27:21 +0100 Subject: [PATCH 406/424] ShaderTerrainMesh: silently reload texture if it has no RAM image Fixes #492 --- panda/src/grutil/shaderTerrainMesh.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/panda/src/grutil/shaderTerrainMesh.cxx b/panda/src/grutil/shaderTerrainMesh.cxx index a0f3c34690..e8078f7b68 100644 --- a/panda/src/grutil/shaderTerrainMesh.cxx +++ b/panda/src/grutil/shaderTerrainMesh.cxx @@ -156,7 +156,9 @@ bool ShaderTerrainMesh::generate() { * the chunks, and the PNMImage is destroyed afterwards. */ void ShaderTerrainMesh::do_extract_heightfield() { - nassertv(_heightfield_tex->has_ram_image()); // Heightfield not in RAM, extract ram image first + if (!_heightfield_tex->has_ram_image()) { + _heightfield_tex->reload(); + } _heightfield_tex->store(_heightfield); From c61b480a41ae5f83f06101aeedb10a41a2ee02bb Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 31 Dec 2018 17:07:42 +0100 Subject: [PATCH 407/424] Revert "bam2egg: support exposed joints properly" This reverts commit dee4cd62017853f44bb54f6e6c764ba2b18cf90f. Fixes #237 --- panda/src/egg2pg/eggSaver.cxx | 49 ++++------------------------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/panda/src/egg2pg/eggSaver.cxx b/panda/src/egg2pg/eggSaver.cxx index 9aba56cc01..451497381c 100644 --- a/panda/src/egg2pg/eggSaver.cxx +++ b/panda/src/egg2pg/eggSaver.cxx @@ -52,7 +52,6 @@ #include "modelNode.h" #include "animBundleNode.h" #include "animChannelMatrixXfmTable.h" -#include "characterJointEffect.h" #include "characterJoint.h" #include "character.h" #include "string_utils.h" @@ -159,16 +158,6 @@ convert_node(const WorkingNodePath &node_path, EggGroupNode *egg_parent, convert_character_node(DCAST(Character, node), node_path, egg_parent, has_decal); } else { - // Is this a ModelNode that represents an exposed joint? If so, skip it, - // as we'll take care of it when building the joint hierarchy. - if (node->get_type() == ModelNode::get_class_type()) { - ModelNode *model_node = (ModelNode *)node; - if (model_node->get_preserve_transform() == ModelNode::PT_net && - model_node->has_effect(CharacterJointEffect::get_class_type())) { - return; - } - } - // Just a generic node. EggGroup *egg_group = new EggGroup(node->get_name()); egg_parent->add_child(egg_group); @@ -368,17 +357,6 @@ convert_character_bundle(PartGroup *bundleNode, EggGroupNode *egg_parent, Charac EggGroup *joint = new EggGroup(bundleNode->get_name()); joint->add_matrix4(transformd); joint->set_group_type(EggGroup::GT_joint); - - // Is this joint exposed? - NodePathCollection coll = character_joint->get_net_transforms(); - for (size_t i = 0; i < coll.size(); ++i) { - const NodePath &np = coll[i]; - if (np.get_name() == bundleNode->get_name() && np.node()->is_of_type(ModelNode::get_class_type())) { - joint->set_dcs_type(EggGroup::DC_net); - break; - } - } - joint_group = joint; egg_parent->add_child(joint_group); if (joint_map != nullptr) { @@ -409,33 +387,16 @@ convert_character_node(Character *node, const WorkingNodePath &node_path, // A sequence node gets converted to an ordinary EggGroup, we only apply the // appropriate switch attributes to turn it into a sequence. + // We have to use DT_structured since it is the only mode that preserves the + // node hierarchy, including LODNodes and CollisionNodes that may be under + // this Character node. EggGroup *egg_group = new EggGroup(node->get_name()); + egg_group->set_dart_type(EggGroup::DT_structured); egg_parent->add_child(egg_group); apply_node_properties(egg_group, node); CharacterJointMap joint_map; - bool is_structured = false; - - int num_children = node->get_num_children(); - for (int i = 0; i < num_children; i++) { - PandaNode *child = node->get_child(i); - convert_node(WorkingNodePath(node_path, child), egg_parent, has_decal, &joint_map); - - TypeHandle type = child->get_type(); - if (child->get_num_children() > 0 || - (type != GeomNode::get_class_type() && type != ModelNode::get_class_type())) { - is_structured = true; - } - } - - // We have to use DT_structured if it is necessary to preserve any node - // hierarchy, such as LODNodes and CollisionNodes that may be under this - // Character node. - if (is_structured) { - egg_group->set_dart_type(EggGroup::DT_structured); - } else { - egg_group->set_dart_type(EggGroup::DT_default); - } + recurse_nodes(node_path, egg_group, has_decal, &joint_map); // turn it into a switch.. egg_group->set_switch_flag(true); From 2bde2baed23f1c0d350b0b0232c0a796326d3d4e Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 16:23:16 +0100 Subject: [PATCH 408/424] device: a couple of fixes with Windows device input --- panda/src/device/winInputDeviceManager.cxx | 1 - panda/src/device/winRawInputDevice.cxx | 102 ++++++++++++--------- panda/src/device/winRawInputDevice.h | 1 + panda/src/device/xInputDevice.cxx | 12 +++ 4 files changed, 70 insertions(+), 46 deletions(-) diff --git a/panda/src/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx index b7fefc7c3e..765fb81816 100644 --- a/panda/src/device/winInputDeviceManager.cxx +++ b/panda/src/device/winInputDeviceManager.cxx @@ -277,7 +277,6 @@ on_input_device_arrival(HANDLE handle) { 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); } diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index f49a7e0d7f..eeb98f112d 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -604,13 +604,16 @@ on_removal() { _is_connected = false; _handle = nullptr; if (_preparsed != nullptr) { - delete _preparsed; + free(_preparsed); _preparsed = nullptr; } _indices.clear(); _report_buttons.clear(); } +/** + * Called by InputDeviceManager when raw input is received for this device. + */ void WinRawInputDevice:: on_input(PRAWINPUT input) { nassertv(input != nullptr); @@ -621,59 +624,68 @@ on_input(PRAWINPUT input) { 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]; + process_report((PCHAR)ptr, input->data.hid.dwSizeHid); + ptr += input->data.hid.dwSizeHid; + } +} - 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); - } +/** + * Processes a single HID report. Assumes the lock is held. + */ +void WinRawInputDevice:: +process_report(PCHAR ptr, size_t size) { + // The first byte is the report identifier. We need it to figure out which + // buttons are off, since each report only contains the "on" buttons. + UCHAR report_id = ptr[0]; + BitArray unset_buttons; + + if (report_id < _report_buttons.size()) { + unset_buttons = _report_buttons[report_id]; + } + + PHIDP_DATA data = (PHIDP_DATA)alloca(sizeof(HIDP_DATA) * _max_data_count); + nassertv(data != nullptr); + + ULONG count = _max_data_count; + NTSTATUS status = _HidP_GetData(HidP_Input, data, &count, (PHIDP_PREPARSED_DATA)_preparsed, ptr, size); + 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 } + 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; + // 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"; } } diff --git a/panda/src/device/winRawInputDevice.h b/panda/src/device/winRawInputDevice.h index c5cba8c615..8c880604be 100644 --- a/panda/src/device/winRawInputDevice.h +++ b/panda/src/device/winRawInputDevice.h @@ -34,6 +34,7 @@ public: bool on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name); void on_removal(); void on_input(PRAWINPUT input); + void process_report(PCHAR ptr, size_t size); private: virtual void do_poll(); diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 42f9c4fbef..4621d467b9 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -161,6 +161,10 @@ check_arrival(const RID_DEVICE_INFO &info, DEVINST inst, return false; } + if (get_state(_index, &state) != 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)) { @@ -205,6 +209,10 @@ check_arrival(const RID_DEVICE_INFO &info, DEVINST inst, */ void XInputDevice:: detect(InputDeviceManager *mgr) { + if (!_initialized) { + nassertv_always(init_xinput()); + } + bool connected = false; XINPUT_CAPABILITIES_EX caps = {0}; @@ -225,6 +233,10 @@ detect(InputDeviceManager *mgr) { _is_connected = connected; if (connected) { + _name = "XInput Device #"; + _name += format_string(_index + 1); + _vendor_id = caps.VendorID; + _product_id = caps.ProductID; init_device(caps, state); mgr->add_device(this); } else { From a02920bb42dbd3dc0c7e6e83bf0fd6c14baee50a Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 16:24:07 +0100 Subject: [PATCH 409/424] device: detect XInput devices on start-up This is needed to handle cases where XInput is being emulated, such as when running via Steam with the Steam Controller active. Steam's shim does not send out WM_INPUT_DEVICE_CHANGE events. --- panda/src/device/winInputDeviceManager.cxx | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/panda/src/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx index 765fb81816..033a174f6b 100644 --- a/panda/src/device/winInputDeviceManager.cxx +++ b/panda/src/device/winInputDeviceManager.cxx @@ -82,6 +82,47 @@ WinInputDeviceManager() : device_cat.warning() << "Failed to register raw input devices.\n"; } + + // Do we have any XInput devices plugged in now? + int num_xinput = 0; + HANDLE xinput_handle; + RAWINPUTDEVICELIST devices[64]; + UINT num_devices = 64; + num_devices = GetRawInputDeviceList(devices, &num_devices, sizeof(RAWINPUTDEVICELIST)); + if (num_devices == (UINT)-1) { + return; + } + for (UINT i = 0; i < num_devices; ++i) { + if (devices[i].dwType != RIM_TYPEHID) { + continue; + } + HANDLE handle = devices[i].hDevice; + UINT size; + if (GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, nullptr, &size) != 0) { + continue; + } + + char *path = (char *)alloca(size); + if (path == nullptr || + GetRawInputDeviceInfoA(handle, RIDI_DEVICENAME, (void *)path, &size) < 0) { + continue; + } + + if (strstr(path, "&IG_") != nullptr) { + xinput_handle = handle; + ++num_xinput; + } + } + if (num_xinput == 1) { + // There's only one XInput device, so we know which one it is. + on_input_device_arrival(xinput_handle); + } else if (num_xinput > 0) { + // Just poll all the XInput devices. + _xinput_device0.detect(this); + _xinput_device1.detect(this); + _xinput_device2.detect(this); + _xinput_device3.detect(this); + } } /** From 4bf10925b399753b62f5ddd70c4278d579ffb944 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 16:31:11 +0100 Subject: [PATCH 410/424] samples: prevent duplicate connect assertion in device_tester.py --- samples/gamepad/device_tester.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/samples/gamepad/device_tester.py b/samples/gamepad/device_tester.py index a3f70b96b4..ee2fad6c30 100644 --- a/samples/gamepad/device_tester.py +++ b/samples/gamepad/device_tester.py @@ -113,6 +113,8 @@ class DeviceConnectivityMonitor(DirectObject): self.devices[self.current_panel].show() def connect_device(self, device): + if device in self.devices: + return self.devices[device] = DeviceMonitor(device) self.switch_to_panel(device) self.create_menu_button(device) From ac8be1a2e684f76bfc3ebae93381c711ef80b749 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 16:32:04 +0100 Subject: [PATCH 411/424] py_panda: fix int-to-enum conversion in Python 2 Fixes #498 --- dtool/src/interrogatedb/py_panda.cxx | 58 ++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index 7c2bef1ce7..6ebfff5b4b 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/dtool/src/interrogatedb/py_panda.cxx @@ -286,6 +286,43 @@ PyObject *_Dtool_Return(PyObject *value) { } #if PY_VERSION_HEX < 0x03040000 +/** + * This function converts an int value to the appropriate enum instance. + */ +PyObject *Dtool_EnumType_New(PyTypeObject *subtype, PyObject *args, PyObject *kwds) { + PyObject *arg; + if (!Dtool_ExtractArg(&arg, args, kwds, "value")) { + return PyErr_Format(PyExc_TypeError, + "%s() missing 1 required argument: 'value'", + subtype->tp_name); + } + + if (Py_TYPE(arg) == subtype) { + Py_INCREF(arg); + return arg; + } + + PyObject *value2member = PyDict_GetItemString(subtype->tp_dict, "_value2member_map_"); + nassertr_always(value2member != nullptr, nullptr); + + PyObject *member = PyDict_GetItem(value2member, arg); + if (member != nullptr) { + Py_INCREF(member); + return member; + } + + PyObject *repr = PyObject_Repr(arg); + PyErr_Format(PyExc_ValueError, "%s is not a valid %s", +#if PY_MAJOR_VERSION >= 3 + PyUnicode_AS_STRING(repr), +#else + PyString_AS_STRING(repr), +#endif + subtype->tp_name); + Py_DECREF(repr); + return nullptr; +} + static PyObject *Dtool_EnumType_Str(PyObject *self) { PyObject *name = PyObject_GetAttrString(self, "name"); #if PY_MAJOR_VERSION >= 3 @@ -337,6 +374,7 @@ PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, const cha static PyObject *name_sunder_str; static PyObject *value_str; static PyObject *value_sunder_str; + static PyObject *value2member_map_sunder_str; // Emulate something vaguely like the enum module. if (enum_class == nullptr) { #if PY_MAJOR_VERSION >= 3 @@ -344,11 +382,13 @@ PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, const cha value_str = PyUnicode_InternFromString("value"); name_sunder_str = PyUnicode_InternFromString("_name_"); value_sunder_str = PyUnicode_InternFromString("_value_"); + value2member_map_sunder_str = PyUnicode_InternFromString("_value2member_map_"); #else name_str = PyString_InternFromString("name"); value_str = PyString_InternFromString("value"); name_sunder_str = PyString_InternFromString("_name_"); value_sunder_str = PyString_InternFromString("_value_"); + value2member_map_sunder_str = PyString_InternFromString("_value2member_map_"); #endif PyObject *name_value_tuple = PyTuple_New(4); PyTuple_SET_ITEM(name_value_tuple, 0, name_str); @@ -365,27 +405,39 @@ PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, const cha enum_class = PyObject_CallFunction((PyObject *)&PyType_Type, (char *)"s()N", "Enum", slots_dict); nassertr(enum_class != nullptr, nullptr); } - PyObject *result = PyObject_CallFunction((PyObject *)&PyType_Type, (char *)"s(O)N", name, enum_class, PyDict_New()); + + // Create a subclass of this generic Enum class we just created. + PyObject *value2member = PyDict_New(); + PyObject *dict = PyDict_New(); + PyDict_SetItem(dict, value2member_map_sunder_str, value2member); + PyObject *result = PyObject_CallFunction((PyObject *)&PyType_Type, (char *)"s(O)N", name, enum_class, dict); nassertr(result != nullptr, nullptr); + ((PyTypeObject *)result)->tp_new = Dtool_EnumType_New; ((PyTypeObject *)result)->tp_str = Dtool_EnumType_Str; ((PyTypeObject *)result)->tp_repr = Dtool_EnumType_Repr; - // Copy the names as instances of the above to the class dict. + PyObject *empty_tuple = PyTuple_New(0); + + // Copy the names as instances of the above to the class dict, and create a + // reverse mapping in the _value2member_map_ dict. Py_ssize_t size = PyTuple_GET_SIZE(names); for (Py_ssize_t i = 0; i < size; ++i) { PyObject *item = PyTuple_GET_ITEM(names, i); PyObject *name = PyTuple_GET_ITEM(item, 0); PyObject *value = PyTuple_GET_ITEM(item, 1); - PyObject *member = _PyObject_CallNoArg(result); + PyObject *member = PyType_GenericNew((PyTypeObject *)result, empty_tuple, nullptr); PyObject_SetAttr(member, name_str, name); PyObject_SetAttr(member, name_sunder_str, name); PyObject_SetAttr(member, value_str, value); PyObject_SetAttr(member, value_sunder_str, value); PyObject_SetAttr(result, name, member); + PyDict_SetItem(value2member, value, member); Py_DECREF(member); } Py_DECREF(names); + Py_DECREF(value2member); + Py_DECREF(empty_tuple); #endif if (module != nullptr) { From dccf8074fde6bbfd394590b47499f2ade9be8af8 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 21:49:38 +0100 Subject: [PATCH 412/424] android: fix compile errors in androiddisplay module --- .../androiddisplay/androidGraphicsWindow.cxx | 32 +++++++++---------- .../androiddisplay/androidGraphicsWindow.h | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/panda/src/androiddisplay/androidGraphicsWindow.cxx b/panda/src/androiddisplay/androidGraphicsWindow.cxx index 431f9419b7..cc04022e81 100644 --- a/panda/src/androiddisplay/androidGraphicsWindow.cxx +++ b/panda/src/androiddisplay/androidGraphicsWindow.cxx @@ -55,9 +55,9 @@ AndroidGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe, _app = panda_android_app; - GraphicsWindowInputDevice device = - GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); + PT(GraphicsWindowInputDevice) device = GraphicsWindowInputDevice::pointer_and_keyboard(this, "keyboard_mouse"); add_input_device(device); + _input = device; } /** @@ -486,22 +486,22 @@ handle_key_event(const AInputEvent *event) { /* int32_t meta = AKeyEvent_getMetaState(event); if (meta | AMETA_ALT_ON) { - _input_devices[0].button_down(KeyboardButton.alt()); + _input->button_down(KeyboardButton.alt()); } if (meta | AMETA_ALT_LEFT_ON) { - _input_devices[0].button_down(KeyboardButton.lalt()); + _input->button_down(KeyboardButton.lalt()); } if (meta | AMETA_ALT_RIGHT_ON) { - _input_devices[0].button_down(KeyboardButton.ralt()); + _input->button_down(KeyboardButton.ralt()); } if (meta | AMETA_SHIFT_ON) { - _input_devices[0].button_down(KeyboardButton.shift()); + _input->button_down(KeyboardButton.shift()); } if (meta | AMETA_SHIFT_LEFT_ON) { - _input_devices[0].button_down(KeyboardButton.lshift()); + _input->button_down(KeyboardButton.lshift()); } if (meta | AMETA_SHIFT_RIGHT_ON) { - _input_devices[0].button_down(KeyboardButton.rshift()); + _input->button_down(KeyboardButton.rshift()); }*/ int32_t keycode = AKeyEvent_getKeyCode(event); @@ -517,12 +517,12 @@ handle_key_event(const AInputEvent *event) { int32_t action = AKeyEvent_getAction(event); if (action == AKEY_EVENT_ACTION_DOWN) { if (AKeyEvent_getRepeatCount(event) > 0) { - _input_devices[0].button_resume_down(button); + _input->button_resume_down(button); } else { - _input_devices[0].button_down(button); + _input->button_down(button); } } else if (action == AKEY_EVENT_ACTION_UP) { - _input_devices[0].button_up(button); + _input->button_up(button); } // TODO AKEY_EVENT_ACTION_MULTIPLE @@ -549,16 +549,16 @@ handle_motion_event(const AInputEvent *event) { if (changed != 0) { if (changed & AMOTION_EVENT_BUTTON_PRIMARY) { if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) { - _input_devices[0].button_down(MouseButton::one()); + _input->button_down(MouseButton::one()); } else { - _input_devices[0].button_up(MouseButton::one()); + _input->button_up(MouseButton::one()); } } if (changed & AMOTION_EVENT_BUTTON_SECONDARY) { if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) { - _input_devices[0].button_down(MouseButton::three()); + _input->button_down(MouseButton::three()); } else { - _input_devices[0].button_up(MouseButton::three()); + _input->button_up(MouseButton::three()); } } _mouse_button_state = button_state; @@ -568,7 +568,7 @@ handle_motion_event(const AInputEvent *event) { float x = AMotionEvent_getX(event, 0) - _app->contentRect.left; float y = AMotionEvent_getY(event, 0) - _app->contentRect.top; - _input_devices[0].set_pointer_in_window(x, y); + _input->set_pointer_in_window(x, y); return 1; } diff --git a/panda/src/androiddisplay/androidGraphicsWindow.h b/panda/src/androiddisplay/androidGraphicsWindow.h index 0842b18f2b..f8b75421f6 100644 --- a/panda/src/androiddisplay/androidGraphicsWindow.h +++ b/panda/src/androiddisplay/androidGraphicsWindow.h @@ -73,7 +73,7 @@ private: int32_t _mouse_button_state; - const ARect *rect; + GraphicsWindowInputDevice *_input; public: static TypeHandle get_class_type() { From c365beaf1134623db30b2e435d2fb4e67ed6d8a8 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 1 Jan 2019 22:07:46 +0100 Subject: [PATCH 413/424] device: hide Steam Controller on Linux while Steam is running When Steam runs, it mutes all input from the Steam Controller and instead registers a virtual gamepad that obeys the Big Picture controller remapping. We want to make sure to pick that one, since the "real" Steam Controller doesn't report any events while the virtual device is active. So we carefully consider the "real" Steam Controller "inactive" if the presence of the virtual device is detected, until the virtual device is disconnected (ie. when Steam is closed) or if it turns out that it is generating events after all, in which case it gets automatically reactivated. This is a bit of a hack, maybe there is a cleaner way of doing this. --- panda/src/device/evdevInputDevice.cxx | 38 +++++++++- panda/src/device/evdevInputDevice.h | 7 ++ panda/src/device/linuxInputDeviceManager.cxx | 75 ++++++++++++++++++++ panda/src/device/linuxInputDeviceManager.h | 3 + 4 files changed, 121 insertions(+), 2 deletions(-) diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index 057d9b14ff..b137888d6d 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -59,6 +59,8 @@ enum QuirkBits { QB_rudder_from_throttle = 16, // Special handling for Steam Controller, which has many peculiarities. + // We only connect it if it is reporting any events, because when Steam is + // running, the Steam controller is muted in favour of a dummy Xbox device. QB_steam_controller = 32, }; @@ -119,7 +121,8 @@ EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index) : _dpad_left_button(-1), _dpad_up_button(-1), _ltrigger_code(-1), - _rtrigger_code(-1) { + _rtrigger_code(-1), + _quirks(0) { char path[64]; sprintf(path, "/dev/input/event%zd", index); @@ -217,6 +220,26 @@ do_set_vibration(double strong, double weak) { } } +/** + * Special case for Steam controllers; called if a Steam virtual device has + * just been disconnected, and this is currently an inactive Steam Controller + * previously blocked by Steam, waiting to be reactivated. + * Returns true if the device has just been reconnected. + */ +bool EvdevInputDevice:: +reactivate_steam_controller() { + LightMutexHolder holder(_lock); + if (!_is_connected && (_quirks & QB_steam_controller) != 0) { + // Just check to make sure the device is still readable. + process_events(); + if (_fd != -1) { + _is_connected = true; + return true; + } + } + return false; +} + /** * 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; @@ -228,7 +251,7 @@ do_poll() { while (process_events()) {} // If we got events, we are obviously connected. Mark us so. - if (!_is_connected) { + if (!_is_connected && _fd != -1) { _is_connected = true; if (_manager != nullptr) { _manager->add_device(this); @@ -310,8 +333,19 @@ init_device() { if (quirks & QB_steam_controller) { if (test_bit(BTN_GAMEPAD, keys)) { _device_class = DeviceClass::gamepad; + + // If we have a virtual gamepad on the system, then careful: if Steam is + // running, it may disable its own gamepad in favour of the virtual + // device it registers. If the virtual device is present, we will only + // register this gamepad as connected when it registers input. + if (_manager->has_virtual_device(0x28de, 0x11ff)) { + device_cat.debug() + << "Detected Steam virtual gamepad, disabling Steam Controller\n"; + quirks |= QB_connect_if_nonzero; + } } } + _quirks = quirks; // Try to detect which type of device we have here if (_device_class == DeviceClass::unknown) { diff --git a/panda/src/device/evdevInputDevice.h b/panda/src/device/evdevInputDevice.h index 994915e40b..ffc3503ff0 100644 --- a/panda/src/device/evdevInputDevice.h +++ b/panda/src/device/evdevInputDevice.h @@ -30,6 +30,8 @@ public: EvdevInputDevice(LinuxInputDeviceManager *manager, size_t index); virtual ~EvdevInputDevice(); + bool reactivate_steam_controller(); + private: virtual void do_set_vibration(double strong, double weak); virtual void do_poll(); @@ -41,6 +43,7 @@ private: LinuxInputDeviceManager *_manager; int _fd; + int _quirks; size_t _index; bool _can_write; @@ -77,6 +80,10 @@ public: register_type(_type_handle, "EvdevInputDevice", 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; diff --git a/panda/src/device/linuxInputDeviceManager.cxx b/panda/src/device/linuxInputDeviceManager.cxx index 45a83bfdb3..8eed3e1cc3 100644 --- a/panda/src/device/linuxInputDeviceManager.cxx +++ b/panda/src/device/linuxInputDeviceManager.cxx @@ -200,6 +200,55 @@ consider_add_js_device(size_t js_index) { return nullptr; } +/** + * Scans the "virtual" input devices on the system to check whether one with + * the given vendor and product ID exists. + */ +bool LinuxInputDeviceManager:: +has_virtual_device(unsigned short vendor_id, unsigned short product_id) const { + char path[294]; + sprintf(path, "/sys/devices/virtual/input"); + + DIR *dir = opendir(path); + if (dir != nullptr) { + dirent *entry = readdir(dir); + while (entry != nullptr) { + if (entry->d_name[0] != 'i') { + entry = readdir(dir); + continue; + } + FILE *f; + + char vendor[5] = {0}; + sprintf(path, "/sys/devices/virtual/input/%s/id/vendor", entry->d_name); + f = fopen(path, "r"); + if (f) { + fgets(vendor, sizeof(vendor), f); + fclose(f); + } + + char product[5] = {0}; + sprintf(path, "/sys/devices/virtual/input/%s/id/product", entry->d_name); + f = fopen(path, "r"); + if (f) { + fgets(product, sizeof(product), f); + fclose(f); + } + + if (vendor[0] && std::stoi(std::string(vendor), nullptr, 16) == (int)vendor_id && + product[0] && std::stoi(std::string(product), nullptr, 16) == (int)product_id) { + closedir(dir); + return true; + } + + entry = readdir(dir); + } + closedir(dir); + } + + return false; +} + /** * Polls the system to see if there are any new devices. In some * implementations this is a no-op. @@ -243,6 +292,7 @@ update() { LightMutexHolder holder(_lock); // Iterate over the events in the buffer. + bool removed_steam_virtual_device = false; char *ptr = buffer; char *end = buffer + avail; while (ptr < end) { @@ -270,6 +320,12 @@ update() { device_cat.debug() << "Removed input device " << *device << "\n"; } + + // Check for Steam virtual device; see comment below. + if (device->get_vendor_id() == 0x28de && + device->get_product_id() == 0x11ff) { + removed_steam_virtual_device = true; + } } } } @@ -290,6 +346,25 @@ update() { ptr += sizeof(inotify_event) + event->len; } + + // If the Steam virtual device was just disconnected, the user may have just + // shut down Steam, and we need to reactivate the real Steam Controller + // device that was previously suppressed by Steam. + if (removed_steam_virtual_device) { + inactive_devices = _inactive_devices; + + for (size_t i = 0; i < inactive_devices.size(); ++i) { + InputDevice *device = inactive_devices[i]; + if (device != nullptr && device->is_of_type(EvdevInputDevice::get_class_type())) { + PT(EvdevInputDevice) evdev_device = (EvdevInputDevice *)device; + if (evdev_device->reactivate_steam_controller()) { + _inactive_devices.remove_device(device); + _connected_devices.add_device(device); + throw_event("connect-device", device); + } + } + } + } } #endif // PHAVE_LINUX_INPUT_H diff --git a/panda/src/device/linuxInputDeviceManager.h b/panda/src/device/linuxInputDeviceManager.h index c5ad6a013f..36fce94842 100644 --- a/panda/src/device/linuxInputDeviceManager.h +++ b/panda/src/device/linuxInputDeviceManager.h @@ -30,6 +30,9 @@ private: InputDevice *consider_add_evdev_device(size_t index); InputDevice *consider_add_js_device(size_t index); +public: + bool has_virtual_device(unsigned short vendor_id, unsigned short product_id) const; + virtual void update(); protected: From 6e951773a6456c4af0f1614ba580170945a21a7c Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Wed, 2 Jan 2019 01:35:18 -0700 Subject: [PATCH 414/424] pipeline: (Light)ReMutexDirect should include thread.h Thread methods are used in the respective .I files, so an incomplete forward declaration won't suffice here. --- panda/src/pipeline/lightReMutexDirect.h | 3 +-- panda/src/pipeline/reMutexDirect.h | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/panda/src/pipeline/lightReMutexDirect.h b/panda/src/pipeline/lightReMutexDirect.h index d1bc022740..384330587e 100644 --- a/panda/src/pipeline/lightReMutexDirect.h +++ b/panda/src/pipeline/lightReMutexDirect.h @@ -17,8 +17,7 @@ #include "pandabase.h" #include "mutexImpl.h" #include "reMutexDirect.h" - -class Thread; +#include "thread.h" #ifndef DEBUG_THREADS diff --git a/panda/src/pipeline/reMutexDirect.h b/panda/src/pipeline/reMutexDirect.h index faf46d0fb0..9a82b728ba 100644 --- a/panda/src/pipeline/reMutexDirect.h +++ b/panda/src/pipeline/reMutexDirect.h @@ -17,8 +17,7 @@ #include "pandabase.h" #include "mutexTrueImpl.h" #include "conditionVarImpl.h" - -class Thread; +#include "thread.h" #ifndef DEBUG_THREADS From ce4985a333f19b6ec1e4a1f38bf0c0a75d69c466 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Wed, 2 Jan 2019 10:18:41 -0700 Subject: [PATCH 415/424] interrogatedb: Fix missing 'static' keyword --- dtool/src/interrogatedb/py_panda.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dtool/src/interrogatedb/py_panda.cxx b/dtool/src/interrogatedb/py_panda.cxx index 6ebfff5b4b..5d17d8851f 100644 --- a/dtool/src/interrogatedb/py_panda.cxx +++ b/dtool/src/interrogatedb/py_panda.cxx @@ -289,7 +289,7 @@ PyObject *_Dtool_Return(PyObject *value) { /** * This function converts an int value to the appropriate enum instance. */ -PyObject *Dtool_EnumType_New(PyTypeObject *subtype, PyObject *args, PyObject *kwds) { +static PyObject *Dtool_EnumType_New(PyTypeObject *subtype, PyObject *args, PyObject *kwds) { PyObject *arg; if (!Dtool_ExtractArg(&arg, args, kwds, "value")) { return PyErr_Format(PyExc_TypeError, From 3be7c63d421396583cf8287470621dff63d8bf7d Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Tue, 1 Jan 2019 19:39:33 -0800 Subject: [PATCH 416/424] setuptools build_apps: Add p3assimp to the check_plugins list This gets rid of the runtime error message about missing Assimp. --- direct/src/dist/commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index a134aad64f..2254515b2f 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -540,6 +540,7 @@ class build_apps(setuptools.Command): 'pandaegg', 'p3ffmpeg', 'p3ptloader', + 'p3assimp', ] def parse_prc(prcstr, warn_on_missing_plugin): out = [] From 0c890f059dd713f60200bd1d60d48a58cabec075 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Thu, 3 Jan 2019 12:03:25 +0100 Subject: [PATCH 417/424] interrogate: Export Python entry points with EXPORT_CLASS Closes #505 --- .../interfaceMakerPythonNative.cxx | 16 +------- dtool/src/interrogate/interrogate_module.cxx | 37 ++----------------- 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/dtool/src/interrogate/interfaceMakerPythonNative.cxx b/dtool/src/interrogate/interfaceMakerPythonNative.cxx index 3964cad919..ca2ed7eb8d 100644 --- a/dtool/src/interrogate/interfaceMakerPythonNative.cxx +++ b/dtool/src/interrogate/interfaceMakerPythonNative.cxx @@ -1532,13 +1532,7 @@ write_module(ostream &out, ostream *out_h, InterrogateModuleDef *def) { << " nullptr, nullptr, nullptr, nullptr\n" << "};\n" << "\n" - << "#ifdef _WIN32\n" - << "extern \"C\" __declspec(dllexport) PyObject *PyInit_" << def->module_name << "();\n" - << "#elif __GNUC__ >= 4\n" - << "extern \"C\" __attribute__((visibility(\"default\"))) PyObject *PyInit_" << def->module_name << "();\n" - << "#else\n" - << "extern \"C\" PyObject *PyInit_" << def->module_name << "();\n" - << "#endif\n" + << "extern \"C\" EXPORT_CLASS PyObject *PyInit_" << def->module_name << "();\n" << "\n" << "PyObject *PyInit_" << def->module_name << "() {\n" << " LibraryDef *refs[] = {&" << def->library_name << "_moddef, nullptr};\n" @@ -1549,13 +1543,7 @@ write_module(ostream &out, ostream *out_h, InterrogateModuleDef *def) { << "\n" << "#else // Python 2 case\n" << "\n" - << "#ifdef _WIN32\n" - << "extern \"C\" __declspec(dllexport) void init" << def->module_name << "();\n" - << "#elif __GNUC__ >= 4\n" - << "extern \"C\" __attribute__((visibility(\"default\"))) void init" << def->module_name << "();\n" - << "#else\n" - << "extern \"C\" void init" << def->module_name << "();\n" - << "#endif\n" + << "extern \"C\" EXPORT_CLASS void init" << def->module_name << "();\n" << "\n" << "void init" << def->module_name << "() {\n" << " LibraryDef *refs[] = {&" << def->library_name << "_moddef, nullptr};\n" diff --git a/dtool/src/interrogate/interrogate_module.cxx b/dtool/src/interrogate/interrogate_module.cxx index 6f7bc4bfeb..663e57762f 100644 --- a/dtool/src/interrogate/interrogate_module.cxx +++ b/dtool/src/interrogate/interrogate_module.cxx @@ -295,24 +295,10 @@ int write_python_table_native(std::ostream &out) { out.put('\n'); - out << "#if PY_MAJOR_VERSION >= 3 || !defined(NDEBUG)\n" - << "#ifdef _WIN32\n" - << "extern \"C\" __declspec(dllexport) PyObject *PyInit_" << library_name << "();\n" - << "#elif __GNUC__ >= 4\n" - << "extern \"C\" __attribute__((visibility(\"default\"))) PyObject *PyInit_" << library_name << "();\n" + out << "#if PY_MAJOR_VERSION >= 3\n" + << "extern \"C\" EXPORT_CLASS PyObject *PyInit_" << library_name << "();\n" << "#else\n" - << "extern \"C\" PyObject *PyInit_" << library_name << "();\n" - << "#endif\n" - << "#endif\n"; - - out << "#if PY_MAJOR_VERSION < 3 || !defined(NDEBUG)\n" - << "#ifdef _WIN32\n" - << "extern \"C\" __declspec(dllexport) void init" << library_name << "();\n" - << "#elif __GNUC__ >= 4\n" - << "extern \"C\" __attribute__((visibility(\"default\"))) void init" << library_name << "();\n" - << "#else\n" - << "extern \"C\" void init" << library_name << "();\n" - << "#endif\n" + << "extern \"C\" EXPORT_CLASS void init" << library_name << "();\n" << "#endif\n"; out << "\n" @@ -361,14 +347,6 @@ int write_python_table_native(std::ostream &out) { << "}\n" << "\n" - << "#ifndef NDEBUG\n" - << "void init" << library_name << "() {\n" - << " PyErr_SetString(PyExc_ImportError, \"" << module_name << " was " - << "compiled for Python \" PY_VERSION \", which is incompatible " - << "with Python 2\");\n" - << "}\n" - << "#endif\n" - << "#else // Python 2 case\n" << "\n" << "void init" << library_name << "() {\n"; @@ -402,15 +380,6 @@ int write_python_table_native(std::ostream &out) { out << " }\n" << "}\n" - << "\n" - << "#ifndef NDEBUG\n" - << "PyObject *PyInit_" << library_name << "() {\n" - << " PyErr_SetString(PyExc_ImportError, \"" << module_name << " was " - << "compiled for Python \" PY_VERSION \", which is incompatible " - << "with Python 3\");\n" - << " return nullptr;\n" - << "}\n" - << "#endif\n" << "#endif\n" << "\n"; From 3e22578529e0986be955de8927c66029930e0d71 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 3 Jan 2019 14:44:17 +0100 Subject: [PATCH 418/424] New branding images for 1.10.0 --- contrib/src/panda3dtoolsgui/pandaIcon.ico | Bin 287934 -> 147323 bytes direct/src/plugin_standalone/panda3d.ico | Bin 48206 -> 147323 bytes makepanda/panda-install.bmp | Bin 154542 -> 154542 bytes models/plugin_images/installer.bmp | Bin 154542 -> 154542 bytes panda/src/configfiles/pandaIcon.ico | Bin 287934 -> 147323 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/contrib/src/panda3dtoolsgui/pandaIcon.ico b/contrib/src/panda3dtoolsgui/pandaIcon.ico index cd6f45759f696b1207a83fd88424c00773ce78e3..3a2c1c33d4b3fecfc9b7a8bf0c2230cc3ce774f1 100644 GIT binary patch literal 147323 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 literal 287934 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Û~10lZYE#7;82<<;Zfzb)MJexmKhp!-)& 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 literal 48206 zcmb?>Wl$YW(C)$A-7UDg2M=y11PJc#?tX9%?iPXvcXtTx9^BpCFK>O{{eOSlt=ih@ zo!#E)u9?~Hryl?S0ssT}?|=Z11Df3d0QJu{8{7ZLCDZ@_ss{i-PX50#Isnj^4FKTd z|BoDw008J!0s!cr_y2p}0vZ6=Zvp@!RFtGq5D5`KRiVhrNCN-s{qKZ_{k-Tnm6(01 z1jtH?sd;3bcDN@|FZymgwfwldK6wxbJ32=Y=7$OsdcMYTlEsFb>z28#q#H{if5xwv30Cf;0d?K;j#q(;Q6A@^`9jt-s}8 zpX2}xTFt$RI(`2zuC@NCI)1;tSVuy@{2~)aE*1p+|IkQAa;zi!d{!HV)#rg5D({+Z z$b@r0$9LtJ_a!}r5CKZYLE6~NYr^9uW=H$|xGHBkow?zZRBN$oP}P8u`+DrA>T8$% zVoIIJdw&P(#d?F`wVB8XklYY}a4s`8HfB8-o4w|EIaTBDb-TJ3w(-@ivF>~*Iz`DE zKG20YWJj}OjpRnVsa$wtWA?_I%-?4+H%Uv|jQQB$iIjudhp@VlO3$H>jMdp|TT@>c5JFp50vc%!YK9(1W{s>A+D zdUo7Q?0)OEBJaq0PCYypQMl#WV)`oeNgSf-6yrNJ{t+k5XM(Mdf6_ z$r%{3}0pXvO3 z>YfCvPXjNiC9S%Ek&#C#)|z7rw(Eoy*UwMvW1%26>%${1ajbegCzoB0c6(=5dWzv9 zDKK$DW8Os2Rsl5NLYy^H(wjJo(D zS8Z!36%JJ&mO`{5Pys;;Is3u(xG;+w|BxwB>4^6k18bG?a{%T!-pL{__Y_}mdj>5` z5EbUiA0%G^;@y9YGWz-wPcOzV19gK7zWJk}d2gR}VAfat_e=(LqO4^zM;riFzsu2R8OqNMO!URIf6Ag9M|;i1t8 zU)~CZTGMQ)v9GDqJFbdMsBfa}BFH9iIAb6NxD;iXZKHLtS;P6dk=q<*#N!%l)k*o4 zHRSO$rqYF;pxe=)59JTiXv*uozVIXwYrD9)b_6pTS-0g$fEwa~K-V7tS1twMw;hK7 zyJ92zpgGyPk~VwW+#j7F(}1MNi;i2|+>dot(q?&$OSh(EcQr{UEJlYnVO;~KgC*;QShfp8BSgF53z zm0L{1Zvxl!3Z-c|q+9+U5#A|U9%`%JNCTH_rni!=%bpgeWnr+p;*t23?P`w-@1Vk|H`$uFpdM$O!ydO&a}3Wm8DQ_v2N;qZ zQs_=89SFp1%|ej|m9MAUU_B1kZ409NWt%ZoN*A6It6l^0oncoNP=Vakt}o9pz5v^;yw?k6)D5bnrOalq@5E$7z-|3}C+cbh!xMcuiWQxxY-9Ta*Lr_Vj+ zSbKFbdz+SYL*Cn3ew(OSilWfS$t>sN5^oa7c+WYvJmRy~v@=MD|!Ks;P#pS#ucaid@7{h%b^es0!kxN$hxM$^hcSk-kcPzJ=!^|{tts5sZX zH9IC)91$0IP7Tt1Qfb?Ia)xsM9Re zWB>8)U%a|LeC+$c{<8@Jr=|)S|^?MmJUfI^%2 zbWbc|1hAy1^;Sh@Q|3ceu}^(@c)9JU7*>!R$zL-D-j{}U<5do1QHBBDT*0B0;=!d_ zce1+1(0ZJa7SAYCxgx>@X2C|)@y6@i_tTTCF22^erwxQk$kE!TW9O6PW?eb%(bSXr?WA;te9reA{?`q79>=A{ zB}3TvnBERmcwi{wx`b@O#;e{Nkxl!qJ1W29BRETY?U~A72)JS$hF1*-`UlnV`R=VkSnB~h4%wg^kF;Uwgz^YVbzNU7)qoNUekSPf$ zcaZwWFA_ER7GJHMR7_&9{hVz!-yrS0skn%fJ$ijr_5-27nVW2Ll~dOWK%eQ-{`iDFM0P^ntMZ*>qvC}&%Bf&Epmy|)<4P8*5rde5Q$ zv%T)`ozpo{Mb^vRJh3c8=FYS<>SZCTKPaz-3+8$jp)dCM03{=1EnS(QGJ6^dwJQQ|Nvdof#ud z_5h}`&^?|1W1;=*#^UhbJ17J!O`|b&!q@X$|HK}!*grYlu`%smYGXtj<=6U(*ZP(s zt@n>2e!r@+sL4jgpdOE4tjTJt<`vXhV{X>eC$#&*rVxw~d}#}JliYkKKI@Fqhb z$0}Sugt1KVG^c;-bL%KmY|imGZEz@by7+6p2}mtEK3!@Fw)eXs(DS*2j}@thqmm`) z+)|Nq^fL(!uF5tlu@8?Z%`hkJOk{ml>b$-)fn@sa4lnvg`=^^O#;5-F_j^kk!G&r$ zhW!eMzby-CCv?7xU8OMk7?AKIQzsw3E*q@0ei0ck> z3Ss8@{IX)K+e6qh@$pp!xFv6*abIvhGOpB}xD&~9=-diV)EvG6^_4YP^;p5o?0Sh zH8!-KU!I?WS$&{p>nq>#vD&HT2yr*O-cd%Fv$=#$RJ@`f3lN*$q#)%6CI=cb!BVk< z6UM_R@Y_iipVu zVpbByDG{V;S};x_G@lX%Bt*tf(}Ty5ZskZ!8AI~B5f-OyarhV7!-t;sovh-Wj~N~! z^zw0+^-uZ0MmDv7TN@;@gajutr6*~$x>3aY)T4EXX)_sHFVwb<)>K>0vFgR?K`I_m zWh``6?fxBAQjb4i;O2X4w;U-CR&8-w$Tm4>byf8O$NZPM1FRbU_G}TJeM7aj`ul;Y zFaQ&E7We5#W1 zaeAcb8vR9@IJycoy7LEpyR|qoML8po$4vmJ@k=%2?uiB-k!kRELz2I}$TN+>Pi{aV z_I&H`Q)IB7WwTqufurg==9--zuHYayxNfK+isXRd0Jn^_b!>u{ZhthZg>Up~HZ_jd zW2jaT!<Ee@x}|h%sRF4}?rV-V6Q;Jta_UUQePQ3KA-wB3A~$m1w`pQXd0`dk-m0Bk*U>QbHs#%S zH;c0{EinTfy2UDV57?06l5h3sU1ktV;DUy<-*MWW8$O>_(^IFKpUf$+F}*}4_zf_F zjr`UN^5+INJ}zyuwE1&ALBBfE42`@598TN5FbEywO8a0if4w~(tdL7>f2#|@lWf#2 zqi{KgDGt|!&cOl>{Be-S7g$n9k?1n@TGZ!F`2m%dU3S$w?0H>>SywZ3$gHTtV|6U0 z3}Z@RL9D9mHy4fc)pgZIXYNG#HqDCTB_p@Nvg=$%%gIxq?fGd!Ui#^hstwN@2B8v* z2u?6P3%{_VSfS4){OL++*BXwDDrrcr1i#M$%mlJr$w?=d?EXsGPW~6M7o&(WPqaz_ zO3dACm=^XYCKKamMn(l&c|%;HQnzkCv0(iy{8z~&Bvv{`$`?gz%0;OMytzd+7QX$4 zn*D=Yf{!3`xP1ni6E9egqp1k@dm~IH(SPXmZjAxa9A&Y+iaCZ;!j7C!#mR3qB#2 zNt)0>X&C~okVmQl1|R#bj~HJv(L@J$qesc@$E`-S5u{6xmFATiC#FU;ONnCjOw(Ug z#_1aqF+H_UfIssBVN&sD3t#FOp7|}eyF5Y;?WIRNKp|DZgW=rY-o>6u>;pDC`{f3& zyKqK4qsj`R`#7DRohcm zv^pC3m!075Nzq`QFXF?zbCW_=!Ed+Rs&`g(7NMEb#t1EU2KCk!n_EA>C$m_?g3hHn z(K&7UWO05rjpVe(FNq2w@0IaN#Y;{0jM7~%uw{s*Owy9t(4B|q@JVWXKuT&ppphWr ziX*~${7mvGrt)?evd}F-X;E9$uL#7|p8+8>Q_Cr8=myrFYe1vE_|Qi;EWIz6Wn+YmK1N#q1og(?C;T`wF& z`#=!rFXV)zApcu|Pgo$4E#Lv52=7*Tw3$fe8?f=V#(L8qIEbHJ^4)qVt+BtJgh^2R z7Tl})bt|l&tmq+3saw>!4L1aE)E3z>a3f#nOy7TsU-rVwx-6(^#e@Wx z#hJg7YJt{^fa1~O=I0RLcg}Ab--(w>t<&hiS7*ye_2it1g+gx#`RGr;27JcGsnJ*a z1tTFUc`gfnXRc5;Uxysr~|T^J5Kby^b5O_B?N5E$BBE7g8Q?XhSXxqPAk__kq#lhbj36 zidkc3^b#8S&nb{LaO0_$qFe}6aw;r7ErcVB9&;i^(V%1h0-7MSa&ffk)HYTsYkC|o zl_N3edZhUPVM3nZFX-*>_5mdH--yT!ee!A&S6wr+pp!mPd%@h7IpcDIF~{3SA{+f# zISS)`qsPGA*n#J&{nsczDrGXYY1fi9kdSzuIWWm3kvW*eg0UJcAl8%}V&(|jQbKx> zyH{g_X>=!He#4f=E#n~=jZp#&Cc1G=k{&`L4EzYJy6o=spHddbto%tSbWueHiwK6I zwEIG%2?8#W}^j~ZC#W!Vm&-UG>?K~^J%b?xA3qS~{w6w_Ro?6wc$qUX?uT*QEuko`b z>$<-o+m7wJWeP(jGkZH$#BVilX~e*deAY8kH%r3U^zsl^t{R1-oDdZO?qd1%OVbr7 zf?>~Wsc$8rngv`?uKBZ{e##0Ies4MrV3kOcmfmpC$zp(MWQIy!1Jn4G#69|6R4L!8 z+B@u$EpnQL+1E;PL*_zQ95e;Q(F&4bP5Azdn4Ay?N^L10U#n zWxu(vh1oLFl$pheU^W&?F<79A!6XdZdrO}2Q_4~Uun`HZ5ra}vUL)PES&*QnLVlU0 zkBv)M%Lm3Ic&kE@w-!4LqFp8m5=$HMbHv?t&S{LGKww0?Y*$4HI{tpoIT2Gpx;PIN>+z-diZpfC>S{dbiZDOWPVaBkyIsV~ucekiH--4kCA1f-c zx551=vE4`7mT*|!ylqL+gsz-Yn1LYMbInzgQ|D%NWJZ!L*Usm|{QT;7WBUL8%ZVB-FQ zP9Q^C1GU3>O}G~sf0GJ{jTxj-ys;`QCKv&W8570ixsMngMXmyt=taW}Z#p^LhG%?M zc~!ntXLsXcjot13ZcAoBvJiu{r1yf==*4lLw1vFU7QUyQb8sJ+5!jO~Lb6)h!U{t7 zInPIPc)c}bxS6d_;rN80<;H&l>q-`wt%?-KT}reQLa8m6z^OOP>?9FKe0DQoh|ZlP zlW?1ZC;14ZV)Z>}kRitqejol_`b}g9F^-u2LlpLcd`uC0$TCN6ysk--auL9Uo=PTL z=JY+U4tJptbdwqS9KPx(|K}~{_O&!KgCFVlK7=UxXe#m4z3o3rX#2sez{Un{Y2RHg zZQiQ9)z<45PbTZv-}zYw!_sb-$1C>Rnm-VqxLjxR?D)BCQV$hl4{Zm>&ua^u6e0DG z8QmQrUqm=mp!|ZAc3Hy#l34uVS+S%@zA#aCWkaswAc-_jadCS0fbdWi>>JB^oKq6L zQgrdl&5o7FCtqFSs>zvuKfmOH8Cq2nMf=tBs_|-SJ7Fy?AZM%T=4qDN>=z^N-(Nn9 z8FAwUPwB;EyLnKVPv=M*RQ750$+em}NliY#-M9>J zkbQCkn#f$3-S9*d=X_}}xQP3v4u`py>WjQ(*Y$%=Bq(zCsAA8JKK^0vEZx@ctMty2 zgNl3Td1aY&R#{1rk#K*y=t@}982Kf|`W*J)WPgwi)N%qSpj&J*{O?D}w(=e}2BP5f z1OBmDw>ja5!V5ZkSd$n1ZF3>T)Jfv(7x#U4%&iW7V=EnH#-j-=aj+?s5yMD{Qe&co z3Vo(4Kq>kW?3)QEvqpnrQYa2D=5Ed?^0MSMQsKHEMescG(}g+1V;|&za3dI!3jIY0 zuJ$7ti63f9Vb(BB==O4bMf@!+lS`PdxsTy3G1ogX)@Ro@tJNi_G~O85)s$}UyCM%i z`!ETWk3OV#2t!gh$~s1<%TCSw*ro8?&HURMA6LHu;Gz0-(A%@xFsLL2<jrRJJA_CPJ z!!1a=D@gSw(P^w~G~~ZICp-@AS=l(e)rnG&uuuD7&GN#wUobj= zR6px8g!}_xA`T*D8V_qc$0Dnc$N=(W4o%LbKC-t=|FL{DyMv)4iKJUuo_lQBlX$xf z(O-*DT>jzR3bAI#Avqh>aa~;OkyGfmKca>@{gprm!mxP=L*_6iut?=(r>(Zcl1*5V zAY!tQE4as_w~pvBeUo2|(%N5R&eQz@V~31yGXf@zGLq5`u3HwLBA^7D57tTWn+Tk{ zsPO}0om#p@-V0+dM|1sxI8(v-%RA*K3<)#blh#BCds3j9O9{t#3o%K0m7L(ohr>{D z9gjUpllFiiE-Jn2*juo?nu@yDLmXW#JxO0ljr(^zT@uDN=b^J`VCj&uP)n}z>Dm6_ zQ4`&s4eSb{2}&-q_%@lvWPD0pR+~p!53$d)d2UgK?H>5un|c+9EDA5ilesl4{cv6N zzBqZkFw8r>_r+QO>5x6vhf7Ghq(geF$w43HWm;Ak*FRvWk@A)kT5o5H7o848eCp?f z3d0KVQeDKHEe(YoZ+&C(<6{`=Wf|E<-xgLC8@pS=-5US%ijxU)_CyM@_KS8JiOBdc zXVomrpVx)1$6O2Tt&aD~5Nb}dxjU3rKPj0)K6k$)|Er5G8R-=*nuD3F>~a=|r8PD9 zLk6>S^DKRTFCt~(+JnYgG<$O{g{bf5tT%%G&#voVodTn|=o;!(D%8_6a*GP72H_s; zcV+nCZI>@$aMV&ZKA8HMLxm_>k&)*!*dO<~zOa2V1s`$}89@ee z|CbB!3F}{H7JFXZ<(r_OP(aVb3%MHbe$K#kAnAu77{cSu;dK@NK)*-+uZ9UjceOx-j*AQUp~~dLaK5OjRUuNyctW z+jq|O3OS!xnH9|1oal};1OWDQGCmhO6sPgbMta|rk2s1sHn3O!SGyqouS(#*v;z?(K0Nxi9P*@_`63n(i(qp$3c9)FqjHDU7G9tZHQ$)_X~XX+*hYK9 ze{eYp!A_){4i->Jf0-WERD03sS7bK0Jf}^0X6_{Q%^79vgWPlQ;p!>J_an(&PtuF< z+cq@fP!YG+4Qj(*srOX<7dEu@x}%hEBwCqEy&6hw$C0VW)z~!&@7#&kRXQkJexMir zU+n^k)Tp5=@`n{=R24s3%Ao2z(W#WlspPs+ZkDSfW#N7(cYE?gr7S-LJG62cSiqF( z$TgDkg*0o%&_8@4Z!HJaozjowywDDAX>TX2l-1GOGg@r)qyyScqT3c?Ygl^98 zp0;`QewziWz|vDmcWVBvrfy`ZhM;Fk`c73Q`}O5Y+SB60?Vom($xNx_p0dLzg(2Vq z0F(=5jd6TkA^_*`O&;C~an|85L05o?&nl!s7jA2fPteQYyft3G#3I=Fe?i{V?R6w( znk@pB=2@PXNE|5$s9s@}(|tkyWbFvzyYG4cK50Ih0{<(25_aM?QNQ&9Fuybh$Fl`M zBb*!j|LEfAlL{gN4}=C;e8pYUkYg5r&a_>*IDT)fWG!KB^>47dbN{E}@cH#i@FrFz z6}6lk(+X^1yRW^Ul)X{PQ91$fikRH6jw$J2Jbbs?r#&taitvB`{!P!wfnBxcR`k{g zIb7la($^0)0A*F;#?6-N9fd#tKZyfuF35eqREF|T1VStGPgwX5TSJR1En2~qmOWe! zO;pY@-=O8H0q@0iwwa-pMm!2kE0DkR%8ZwJ=B-c${_ruZ#7;3UJ(@yiy^ zj!xhwQ1oD~Iy6(!Ypa$0u)2dymIG-u{5@S|Dl*-XiO3@=n6bPD34xOiOR+O@#Amr? z(z!B<5{FwPA$}bRh+CXS4uYj{DD&k>T_Bwg*Z7{$15@h?VSntLFbg<`%ZGn7z` zXoNhV5)XcplkHGn=?XOWSs4%71xb8hEVjsReFf@Oj+gVn$f(GlXrU(J=Pc(5R6b2Howi7$qb}uD zC@MBj%g*fMQv5bJh>@YxsZvl^76o4aAyn(gDLxzTF(%&PLXpfH1q_+mPAqRb0Kvyf z9%$bo2(*&D+9w#l^A~fRU|4%>M=JDvRGnC)6N^Woov=`@lAEAls8e2=1-c!?V0Vse zmNJPWaD6l(1r{Ly>7mqMq3i1d5)!teQq^w{XAqz?^DYrOwJKDv3Z8rld&f?Rf%G;< zn|zT9EXvl}f@_7iJm;NALi0NGAZP`c5$6f12wg1I1e7070o6i(767_fnnj?!B?U!= z-!@3jc7&$s_+}_a%Zs75K|ngu?^Y0023A~YJ)Z2|`rKz!&{_;JDXf9#at69sJ#{Cc zVk334vwIvF(j{Is(Rwxz&|Y~VDU2SIn5O7go?m>=1fR#8Z<5nk4X7y%0gUjU%(FY# zot>R%Wd#m)_CRWCka#?>CB&@2^6ZE@buw`r4?P1n6Mh*s4nsm4Lx%t3m;CE`4m^&< zo?<2`uB$s21)DWD`0EQwIp?4wgR=#li=`o*lw6_-hb1xv-F}_L5f?m)1_xAYV9LlZ zOGw?O9s~)0Abo+o9G$4FeUUi~(8y9i9cOpWG$~=o3Y5Wnmnj1Cr4dwXTVVGK0i_^j zZU%puSR6h}4cD+hmLOEnv>-2^K%hdn10KRl8cnMvI)3~+p$^{5*{2b^Hz0DaYRnp^ z+*h8^>t3rFM+f4ts|Z;dTG|$eEzD%EZ1B0A`rTcvs{^+HD`)$z@b{I5iZCD;nD$omdciOPhRjgAhjnNxxPd zsDQ8+Bj$*5Uga#wkW_niWn7WiaxHoCGI;@!X0567`;I_M8)!NBkV<1Mp1xG2{E+TO z8)E4h3(Z@RdkUVf_r`%c4rXR4ot-)T&@z|nTLLMBb0@CHF|fwQSYsMx(O_^qYw{5p z{^oiZ@9!g6FfDGIaudw7<$~ODWC5>aVD#sVYR)kf#QkY8(6w51Gembb#j2Jies0rK)#}68fj>l zJq~fNbe6$L9QwHZHheMhc!_7EvD*xHhCUeLXtKSPHAV4)o z!~ua73u)=;hE84?N9jM&A*s0#nVHMAc>DkAwyDA=0do*9r#&_E~44-MD&WS4G-lUn$5UWd5Fzp{N_2hgY$MCJQ5w&(s! zr;85c!-AY#H?`Lumhd@+zsThUBaVeIgA;3a^5weY7FHMhLozyD9R%mB!xN@fOR~ZS zAjS%1WoV|uunezK&2Fh)oi-UUy;n}M7m)Hv2xXXO=<7>u;Sw4<)d7KXSmgH9y)>HQ zNAZSsYb)a%k*N}?)J}eI>Bf&19-W-!wKd5I ztc0dOx`6E93*zXofh|P=y+xHa)i}KiCC4|r-#wG^Yl{3853#X>oQsv6uXklh`7QxR zHY}I5_Wq~z;Ew};#fv36`X;t_Ir@#GN}+~z*wKbbM;iwd$$5>+4%TGpDF&50C`t{* ziQe!qhnjjQWjKVflmNp=1&Sex%Wd?=U}7?Sn1ej& z6%(B7+yjS?Y_u%IhAw8&p|(J#-NR!G<3TXRzkegY7@16sWU;g&PN>0uPWIQ zy!2#DRUlOQL{ViBf_+$GS&d;Lhldf?y>1j2&?<~A`a%OX|FwF>5xF^^kI^rn-S@8t z1Cq{~8@o(fX4V^ZEian8Sjqjw>$+3V1GBr9y!EOv7J1a>@~XBmKxy~r9V#+$kUz?R z6`515G%9z=KrV$uU@7(&kqWgh$OZ`Hg$2M;;AbwGm0@IKlrUUs+3MxpazIuzVku+oGFRaOq&Kfkaeh7tGfWDX zB_9e+7KTH6nCgfz%k(ZDj*MWgfVh|A!*{I==L42mb-I>L=!@%b9X+qFLTIa9xf*(& zpoWu$-$e8(h|Jk$qfoI9x0A6`+D&v88Pc1mBeg0i1el#w@A9-Ro1S`pdEyadf9}8b zMLuJx8O%y!#lBv%OL9Z&qo8lSpoMRoO#VvJZYr{jo?I9{bD;usA{g)xVk3cYY(FG= z0e1MSUx+hHOX*o+qd9~Hql%79qNHFK?G3zJkj2SyDz&P5=YHreq9YQ<2;9sg9^u1(c}^7R*JK^hNCs zW~)#5`y3_RVd(AgLnv!6ZIfKa{5EYLTLF)SNjk)&W`p;(yai{g3ZDUqusFR`A#iWE z^m~?oYYI@Cv7_wy=gO>frHUx8i^v}wk1}qGRXsCtgA5cF-S)8vXK8-NBj`RXDw8@` zVcl$G+e0dSoiG`GgMi=|NnqlQ%qZHe6#SZos$&B@25lbs6|)+2(g9qQ_W|}VYk}`r zb*pciq_5E;fKjf`BDa&|GcLziE{dD6llz7Tljq9Iq{SnkFOE_sK+>!c4q`a zoG&-QP!)e~Dx1q9bmc!$r+kBD@F{M65%ODGSBjH6dBR@3VOW@Z6Q+h#LeJ5t46acx zqaxN^+aP;6OMXIpviUoj%iCKc>Aa zAJd?|VqLz-JeKyHx4E`}WeU?OGfIH`3$Vn46v;e8ChCn)=$|>`nnT^?v4Zj>8;8K( z{y8oSLidXmX=!s>#K2oybrIB9`P&Ao82S8z7Cx`|=_?EtWnEFLgp?G>%%%$CUtMU- zFx+h;<(#1JbrFxHx(Ld2TOji|#yrRf7NKJmc#%vVEva89C7X~UIyB<*gsI)g%maH- zSoH`0rX@?HWnlXH`c7M{@ch>pmxJb)1~_jVV_#k>jyn2hcV=k^|HEBQsD@ zIAwMn7a6|o2If}|r#$3Lt-$wzfjvhxuxES7M+%7Y5a1W^bE~t0nM>#(BlZnS>pzYh zPVViUD7N7J%P;H3@Dyov#7>z_Ft(!bJ%@+*5+@549xr5{9#Et!ELpJ9X?BR0?@JZ4 zsCRX#wq0bo9{6n?)*A1{#VSPWaQ5WfcfjwoZ_5&`XH+R5)zSf)0J}A!l+)^u7@Kg} z94%kE!fjCO4NfFq_(-&p`VbVJ!dFPn89~xOEcD}5jzJvt!S*!`@n7`lR;XVoEa5|@bjSiz<$|3~ean^LRYbID*D>?a=CeieLBIF9iOVtp+?1g&Y-@GDCa;TS0Wh+U{BX+#L zcTgf1EoYMX#m1@VQsP8UXqx~9LW?k(B`8CfFyVoWp*C{a`GgxM@qa&>=~Z9xehIUe~Li6`^CRN1rRY!GcRjlKhQo-Fq>f7XCd1 z_X`$6UOvTC$TuxzPHy(79}Pzqwz2cxhf|s5#~1qDb2m>yD~)!0dm5P#(g+k)IRb z#n0$4j^|a+51k{b#9lN6Ra`Njp@+rw*MXB*M~Z3uxQV*W$A=jc0u){9XtJWcqOHP#z_vcGD6|t~yh~6y4#yU774L)7;b_>dQ&|F|W zUFL8?zv;krLRqG%Eijr>x{C$w6R4CMK<@iaqg&|Wz%m3A{)lIEHp|9#!gCgUGPHZ$ z<4r``IrgV>U23C8u0Med0CPy!%8Sa*&HY(Xx^sVQP!^F@Ha`Kbo|E*U?U4w(W#vo9 z&WiAL=Xjq}=+pg@dT~RF*jXAI8^3JYctSM)HW|lQu+(k|wc>VBOmQq@xI>r8Ulv$F zOuT0xzhVr?>6eD#=~SzgZHUG4E;(nd+-IG@Y#;nVN#{tdev%0|Z@d+^w&PQgkkAZ@ z-(qh(TaDO4sj5iYUTwL#eW+?>z?$5vwOOkLHLZJPc6dKPn`%^$Y~OZD^WC3)Oq{#w z#*K`QUBA4(bc55n!h&--B*LdMNNP&l-+L^zsZc2XnMo1n0O7lL-=5HkF~8N7Ip z{gpLO%dD81pTS%uGH@~q$`(}jgN%zUjD!r&sKB@9D_^TgPR-5Um*04#=(;T(lr6T? z+w5^9CX9ilqB8dR2xH}__X~gU-*j|pVNxo9kUe4(@``XyoxGmW7BO*gi*~hs@qCQ; ztxrN6%d8t)!qTJDHey^M36$nn#MJ+b;1{S!7XK2jnMKL+h8LW~5RC}mo>yOWf3gGa z`DH~hMfny1st7b?OBwWoMg;1tRJ$dTtJ* zT{q<0J2~z$L3NPOdC!rslK1`*|4#m8DvmB9$<@CpuvgWtKw?COE^%tuLOw7L0oa0U z%!#YJr#9Qs>;0^OL{Xh&(77b$uTiD(%^-s&-R8cr=EJZQKcHAZzO=*|R`6n_kJJAJ znT$Bw$ku-`Vf8vAwg7vUlhfzHTjZA-!`1G0WfhiW;nLH;r_3w6b5DuH{NB-2<{OmB zvp*Y(!q$W{Wz12m++rgBrJ45$LQJoWkO(jKG+mjN?CXpDlRop>I3X*%OT*mco|9WM zbDAhvsAgA$8+7W>7l-E{2nMn%x@+U=1|NF38yW?;KNu{U!p<0frOpRk$$!6fbD0Gq zpXrU(exjJWH~f_#1)A~A<9Xt*J|XAlET3Sf9R26gT8Z^d%J+X3tRI`VI6D{9>)Zt| z2xTQB8}}HcP}k zJ?4#Qc&}FM(ZR?m!9~DV>O};R0$O-Yw?1W*nBR*DcR^d2OLFx6!P%uYl-}hwFDCq6 zNm7zA8nes&i^sJJ7Ch<}-%Jc7Uo%w_A0qK;cobDP1|MnxC{vK!Xl$y&KagLAjzdsT zxw7LE;UQxyK031eM4>|~o!bm#xd77iYh*oA^d%Xn!BE1lhjRpaelN%m=bG46x zVHC-kt%8Dr3PZytn%$Q4Z|epIFZF8gK~%&Rp#yW}e47o+Vx8@jM)LzfKFK-Xz5Y3$ zQt<@Td0$c8Joz&Fs5Wl^ACO(h*6DEf3;mu7gW2J^rNn5VPA5Qiw~g(_db z#wCnTf^a074`xG_F%8NsKSCU=ExFwtHeY+7v?2OY#XJP<0<`@0ZQt@lpJY~n@D5ft zIJ?P3@+8|x7C)f0IJ#AWZxu_09cShI-$X1pYpasEYG=nGRuFf9U)P#H3*fF_R@s|HYp-CH#pffk&frp+)J<1qp@f0rx&t zn^&w9wJg#y=hsv0Gtc10a}LVX9vb3{Nme_0*0I!aw&u!2c|3EEom;N9un}zM(6Yl9 zUhMc6&#wSz>=>azaE_tG26}cjNso7p4bh+s5YuuH_4M16G*K59q^I^Xe5Ds+dG(a!IouZL82@nS81e`3T#vVStyqwE98K zpZQH|_+JYS9=>*wI$DEqs03?w+4%ACA-07j>A3Swcq@(Ph{rZuglg8@97ngzc;jBI znwSgbF~BT9~kyKFCw$SeaDRS_vEl-VS<#3F7%XDdyAZO-PQ zf=_kYf*02XhK_$g72I;&>cUHDTsxDq^%9CO~A2(O@Yg?h0X;;era-r1S0&A#ubhbdb=~Qq)!FrfMl?zwtNb1s^oh%VX;;jC?63jCN#pd z<&6osEYOTQ4rs`anh=1O0QYai_A@Pl#R$-<_{NY;nK|CM^WeZ}{5iW)hUr5bgy%7Q zn*!29!~ZWA;28cI1j#MWlGg9oLLOo;cz^b~hu76#q)4RGu;2}LVHkk&K0lbkLvy_D zwe<25U@x?O2;=Fvt-Uu%Ok|}zvjpBysh$7Xy;9M=(JtX32J2?!cckStl)-$5e?2dM z$N7pCP?F{HAt+wlR^0z|^IeDZGx^tr&!M2{5c)lG{Bxhy$^tyGkCHj%DooxSy8KIr zrK2pz{y#p^GXVj?-rUjuq3*52s%pBpVFW}JR63+n1Oz1nX;A4Bkdp51PDyE0x&>+E z03t2j-QC^Yb@sOpqVN5b_w!uu_x3>i^GOCuK)Y(nd0>JuM7v_r-9K4&pV@G2tv;(2cUlvWCT;ZY{Z0qoWD*jQ4krtk5iXC2AWOB)G$;VTJPU z(cF?ETo@@}{yI89pmT7#TD}{?`5Bq2nlqgkk>Nu6>0JU{4GeG2DhVFdqL|pM0!TIX zBbK}j0&iS~Vm*F*6q+UGj%6l7PTuAt+eM2e!sM`CCY=M+o4I_tDP02tsP@~{C=v!w zvkYUXiHL}{_avOVhKHE8$K6S$FQ<<2iHXzYXg{{=sV=KT=WPJ>-{?)w*EYY%=a8kH zOWu9)FhNvZAqkTVQINJs`uu>LI7U3yhIVsF-eFrnw6$y>ccfJF(o`3y|Fm=?zXs6) zu0N87#pL^|H{TXL!56RIu}yJ_D<~+;q+$qsUgwbdv5nv|I|>L2N!`xP^l6atK6$0G zd0%BcAUd0bY?s~JytcxJ(qdtbP&L<4W)$lUT`cnFIb4R3WAq1ztZ#cKU(d|7pjz)g zb2Zf!UB!|1JRN-?8-^_@rINy|JC2{mSCyAsy4>mmF5dx;u@*{q7iFJEY>P;f+vt`3AZBG~68e^Cia z*q1;YFZNPFlrY#Djx;0o;+(48^hwi%GqV$!$q#UQ)%^PVf)!!alg`)>pt_Rne&NQ~ zZBE|p=gV6~+s5{9><_E5-sY;3dV7C>^RVPXd>9foLQKFrkJco15#+e1!{kW4dk0e= zg9(oSQwzDtzW^H%BajjWjg(ATtYqL^zH(5_b{(ScxEyLd1^(rD7gw!8w)P{;hAZ`5 zyZ6SUO5Qp8;ANwEc6CXPjgPZi?qOAUk>fUoKMR%0KU~a>XLk(pPxvCIL$$ORipg70 zsvNFQd@;1`k?2WX#7Uu1WgrVFN4-}fQ$V0K^)!$-UBIB7KZ8(8^iGVt-H~lr?w!7n z#@+d_4k_$pBrl&ETNSfzLwAGwh6lyS6#PP{sk(Wb-<@x}4-=GC(t$@PjhVg09)(W% zwg`C0Q}19D;k6vOn}W-?@-5y{X=BDI4HKYQ)9*cqCQcl-;Jh?xDw?|Cz{}HlJV@u}~<_ zUYSBSv;qUX5B23ktM{=?w*#v6B^CcIO0>kCijVj>b)wozORO1P)#xlac*2=nr#rg$ zhenIv$AlQI=dpYnMJ<`IVxB&olKrusXy5dsReOEddO2kLWGpcBTaG?LdVWD#bHGQG z+`Car(gTqVl-!6)4BWM}0-}a1Zy|$&K!?@*1VfgSSDV4aDo^Q~{rcX$Ms^oOG zkpZ|g!<4RNlEsIc8tq%1Cg2=*C^9l>#WYGrCTGxL>lAmS#J>BD7$vpF9bSg)vis7b zp#{}w&xm4^lxr%iS~C@hViysE%8CvZgp1tmAt@O>qoe#hQOhm8^I+(yRLo+i&yOcs zk}(;r=XRbCnQt~*Znnft@r61Wbg2^^oDUC*kkj#Xdfm=fwA@Lp!SXcf00#4!t1$Bh zJHptkkw2?JG#DAAX(L)4kTAK|M@wySDY#z*-3#d(?e)-n*;Q%L;uQHdw`rklKc3g! zL#{2QqA={JxR}j~a~gv^F;#})W3hO)?KEN!LQe83zC7k>OoUz8guz?YVT0J)q?Ha1 zVsGc2i^12#6-=j{kjX|0opE`2EZ#w@)XK{_UKEv9V*y%-3wiptJhfkRDB`rZ9`DP)RPFhrvC&{=Q^c45G)d6ymasm-&Nulk!p4BLQ zOwNyW33qc`p+vVBaayepmI+z2Qz%bR`PNz;i}CXjhj*BEY@vLR0VTNPSo1p=WR`|qtP2{*qxAWRC)9z zx!32h5>?n^VN_)Zdk2mwlC#x-5pf?v`xBAdf#LZEY*q`pzFKK517%@&RB>A-oaYq} z9mdDEb=uP<3Qlb19aVWq${cnvAnW;tS`_EU(=8_>cv;~^dUF%^ zC#t-vFWX9@t1Ns4hLO_(@}A`q)A@gY-M;StU1Z*v>)`1%KGqtLvTuv9Bh0{%apC42$P-49Ha;f7s7bW;-oG$m<@z%8Y_)vZfV-@$trk zk{1;3lTe|dZK@Gr(&pbCXJ!0Vc*K-r70HFDBgm=l29>{8h2%@~NHzL??7Ct0pdvZubG)L-RJjd*dYzY_0Rhu6Ek__ECu93zLGV9BOyZ0Z#kJ! zbcgP$a3@EYJgbK!aGnflOXa(PpDig<5tjBsK$vi>-*j2Lb^rBhUVq=~&jwL-7na<# zxacK*4&E}za^K;q?^UYpTI47d2M3Yn5Q{~SAEqfqwqx&+H!gVuZ%mv%a~gNTBgVV8 zxi=^ypBCQH)oHykVyU;hq^PKh+`_`3nNwxWKcp*`3ism(2Mhg%efNCwIMW zO_e2iy_d2+Sz?b4EV%%px5d^M2e;i$=JiW=w;rceFIa+ zX1{>9#bVRabh%<`59z)0cUJ1C#d0H?B9MGF`;s2dI6cD2A+A(-g1_Dg-c#6-W~@Fa zJl=hu1U2sJ?AB_9U)FDD=Qqm##&?-Y+>$D86!Bo|&B>JNBNh>a549KOP1(c><2SoM z$k7!o#X}`P6Aevwu>xP+BX;s98ZYr_uPEnmTqd$-J{)`$Ld?~kp74dfzl24ZYi4CU^d@?Jb zQ8!YIDkO=$_2ZX^N@@^oMMu=an~!`gE3s@JzM8zd#JCfRB}hqY8*0*(RATYCp5?CH z=0uA*G>>FvKFKj$mEdSzxYZ;}GCDY!FpqDDDJx3U1J`czX{Fw0rK}pa>gFft28*4= zXank0)90dETJuO%#{>6Qwzt2OWIR;iVPUV~JgWApcG+w1P^Ik~Q}|31=n=cEo`J^G zyDaO-B=}(Xqa4l}J$*z>T;)3@Dds-bx`cH$Cd+1O>ZWI(T2sAT2?%{oidJwL)U@pE zBC}TUsk=LwsukV8*If@9(h`xbkMZnV?C|X(f*)3j|z8jUSvE>&2z@A z%X2tDewmBEB+6>S)&Yl{6o}|;^XPjKq-C&Ft(WrQXA&Hg^mp2pD{SfOQ&buqWiXCleJyWp#QMj^6UUVJzY;xk-o6hM7r3T zwETn4POASpWT`0h$J7`jX>`Cuc*}Pm4byBO7)`+9Tw>heqUAXHht6x^xAftgiYY#b z^e;%69wBk=gY8zo$CCTlL9sZSs$yky9e?$8l_$1G9#TIGo+?}0zyXVXS7Pwsw-T&7 z6RzjuF_%q*86ip9!-86-7+w)j(P7)<7Fg<6A5vPA7 z#3*2?8U;SX;^s2r$e4z=jKkq`>ogVR|+ z96)`lN__jXnR+Gowt}wG#%N(&bJZm~_vECFy}ge&ClgnP^Oggfy2C=XdW6%h{ zjA^mOu0i7)Bac!$Pw!q7u*Q2wGB5Dz1_cVe08VRKT2A-v5kHk@?L_udT@;}%VYi4& zLn8&q=<%mm3O?Ry-=0k?6vD&X(st1Z>YYXZHflmMN+6aPB}Ubim;l~N@>pF}Vo-_K zH5?TTfM@Vxmk*KQ#%|rrZ=upaJI|km+Uy?GGLyPIVyt5_^;ga7otk& zKahgQ6`eO0ES4vVOom57qHU(>K_L{9Z@V7(guy(9I$f`yGbGS-IBRe%p3^-s>OP64 zlTmC)*S)WDzFy7Xe9Ng|@5KNiW|ln~Il0kJpaNlYqh;E3(7^sWjY{y&<=fmr)JzvI z(xU{4IJvfcaS5qCe_)*=DCj`Fu#Yi9^65msXgQ};AcYdm%|mLYOoS&?u9Lan7xRdw zo8w;XKkyW;CS8f3%~NS^q+?L+h4AIw8a0U|INDgcHSHcNAcpdVqW)bsp~<+OnVW}J zK}$ymD)_(7%*!ifuYgNIQNK~CdH;o%7uV(7?R&gd;gxTPWwRQ&57hW(isv=Q8El6G zG2QUqyuQtFMoB)#_X=x`8I_;G)Ee4%n+Muk-M!SgJ|qqS2t3d{gV%52$TN$=Q~m}B3V`Mdz%K!*A=c-B;Vbo@=(TB)HvOr-z>i+idP0jJ z-CL?$hnL&T1(u%RLi{rc6UqolP4J1V_B+&TShGBEvit2nY!={i%j~#r7nO@m+GRaw zU>(Z3*eK+e8bC9pyoyVj zNu1AKk@<%~(t;<_Wyp#HY-%MK0j8L997G5mUae8tb zk7%RHj4DwRFDT@Qxo0{UO+(eA)nFJt)=Gg34H7tn!^QA@;Mj?13D+2_omF{%cqrbO zgR8s#QAEDX<8Y#Gb49XC2RAvo0_E~>&nAv94K*6LX;dec0CWfo@>&<@vivseKffJ8 z>P?y;BH+e*TcWSpi}JHHisEIMq>?z}KpA_*K)j~P;}00amM%r=NPX(Qt)FT)(PHZ; z>n|VpPe&bjD3Km4_9zL<(sx_6ckw?9&LwI#@}zse#C}6pI%Mv0Ayn4-BX(TOsTQ-Z zH#?XYF;gn3(gr^GU3xiUTS`np=6QdVa*kP-@0u@Bi#TY5|Ck``4z$kT0<&e&+5OZq4OHDwB)-Qo=3P#Bwm&pR&di{M6g7s)y%gQjlRjgtIxT zP{^UVQ+s2wj6cqdliyQ8s6UJ0lRVRaej1ir(z_a8)?Czw)cU(>Sr#)7_-J)4w>K+B zcqrMKdFwTVLgIB#j9e6JAHKj=kAXvMzo>3ghueG}*op9%koye&vJwfHK`{T4%erbC zHFKCOl2wCFCiEE8& zaeE{JHMdioacvJNGIxde#;{Po>nlx1%&zP%8=B|sbCR5r#<7J^HYCm1et9m*$DRC2 zP~97AmV;G9aME(MMK2~^O>6hT5dzJ@;rD$;x7o@G_K!;kKK0REHN?AbnY=&9|(eKCvj<5E)vL z*!g|0u3f+MyqKtyxDUH|#DKT@u>JntLOSt*s_tMtUNWqWv{x9);!JX!B|WuTkW7fbtekxm&i9vwBW}%-hH+px-no9 zH6@-HCTnqW^1RsefoW4dgcg}hSczJdDA(m+dAd``?MSUlC3?OrO48EJDP+&;_G%e8 zJ$Znq?UEnYN#MJ>W!B7snIdK3*b{HW*NKn)CXY7B4t&Cg4(>+BWBC=s&Doe286!V5 zPs*KJ`yYk*+g6HyU9aqWA!0!^`}i}{;?~uwb9Ux(Bi2Vfqk8sUCbNGl4K`We27*m!&EJ&t9p!lpBp;Ao+1c} z(RolmeK(lhr&B6ti2OQdr-dj}+{>8Nl;dvEmBIREc(gImak*Xk zkqDDwcoWOO97iG>X~4Z*s&-1UWwNp*Hqm-Sxa8{_TJ0$b$%h9fS~ zMhr$^M=9~ty}IH3lr!$ObMxH=>*1{pnNESQC8U9JRiiEL{g*|$x;bp@L!LVBQYKv#zCSHWa%X${>>iu;$!#@?)bN4zXK7Xub5=LI z_w(0xpA&QOJ@7TZz^6{v)xt+gpXIe5-xbafr(L9#g3geJa=ZHZ$oN#%@^6&`s;H)T z8Q!%rlW7Y#wUv%Uv>Nu*1)(?^~-xt1tZ?cVXjJGW9U?`qY}&;$;q6d?p~+5eCAu-8 zL~{Hs-qT#EEpx+5MNhb}@%GKZ?cE2;@6RKnd-*v0f-Q$tw&{Tagg z?HAkC2c+ZFf_W9t8wz(I>y~RBub^wj(~C173roo*6x4Y~Bjvp#nncsU7iHu)Bxs2w zB#qK!z(-rqq>pP3$vrekpL!$fi`o4cUS8bpP%3T2Hwwf(UFn_-wGFO9NxA(Avicfs zx&qhy){@9{?J^5m^_lQ0^LxJ=i2HMnRc-8s6{vQ38H(|9Y`~`aPP;o`wpFo?1U|!@ za+d{tFt!u8I4Zp8#Jq*KL87mIE@FET89{=Nn6;Gz!%`W;Z1v zlWVu}AiIz^66|>;imPfp$T3+aPfgcP$iv+ja@RlXq$p*T`rSZcB&GDG>iyw2eLNp7 zATo!pNXeMhxTP3a6c#3-vi;adoX$$TAC^Lai?@UhyLi=Sq0?gE=POoin5YgGo9e70 zy4?;9LlPM+*Cp&myLFGcP| zDB($_Z91wbXUnuG9vq63PTu6XYm)5qX3;iAsnJ_@^^Ut zHWpzDi;c*o2|r^j30-y+3}ZMr+_$jb2#Bg0i2Ar-&rR5kLRq;jEyL)W@$|;wC-36w zCb%EIQG!mNv>MaptXF8i7rO3u67o6+sRQ;&%R`&%Cl5*7*e)+sbqZs2rhc%!Ym^Dj zb33NtUWMN;#LmW6pH%4nqHLWx%3#}EZ^yL;A!)MpT3!{E7<|}(%1cDV?j z1`e7bLgnK}>H4^vpy{6O}i$1ick zIa&)X^JBJMjWwmL*c+U7+o8KATH%LW&IeS)Krm3_T4KKZg5*bB)s~&I@_w}5C)Ok7 z0+piY)|MZ4BYBo+2l?La51xy5rW=6|*0E_FwE5Gy&M7Frr6{Q0GI5)Ho{58qy@cQ1 zV7qQ(Uw>2pWwWguCWvKu>W5l+A?a1Vee>w^awAnWYMp05eni900j^-S7P))dS4BZh zQnKuj_K*94HVgfz_qR1N?TK!MsLl9KpR(9bm-}2+JRJFENc7@`tD?x+N>1Gqi4Cg+ z<)+3%u(4?}_F02O*~NKBIR+sVx_E6UDAihrY8b39Z1QUWiaFY99J-;%SBK=9Ol64u zXf{ia8M+9U>q$xjAwxP&2g6>}y6|UqN%Lok>QYV)5b8rOJd1?`ZJ*c`;O6(J7Wb@| z2QrUrnOu~x#L(#FOd_+NJszc3O(0O>{pR8Iuxs}uw5rR%ouFg`Z+VOJ_?wxDpInH0X)Sa(3#Lc^zgG8cl-e+%~tyNj=8G3Keb?Xr-?0#utq@hL=06!m}57nj>s6kIH zCVp}kZ>^<3k*nF)_W6|E{8GE?=`dHUEDBoYZk5iCfO&P|1ck1bYi8C3#B8}aRfLj* zvNn~XEe^{Df2b4z@=%d}SScG>CJ+h1bbW-4tM*{vdDYJ0@=3cnmzaY5PXF@Q>RS$)VID(fucPpw-(eIS8+*Turt%vg#-M(8VK)Xc{b|SNn+DzmYQ1had z@%Dh*=BtmQ*x^q4=}w z^jU_j>SG);@Y^Gi0wEzG@-kTiwwqiwo3!}tjh3owb_-O|%$oI35b_Sihu^NA+cz!4T6f-~k6`#98Flg{6k4B3e{wg)k^0>oAXo5$cJzHj zL!R+*ITBhWduYS@)n|z`#tm&0;%Vv8xZh-Zq{4t3 z8+nVg(r@R>*SvQp{+QJP>+gYQ1_2y8_4Wm^MVC^7_-6I-PSWRYO8MDK0lgpVvY*=dmD|{{7>lZRcBKP43VMd%P8V zeF$E()d?yQc|TY7Iq4aJ(+LYY@z%?W)2~aEs7H@eC5l7rq`yTuKv(#oy9mywtH)FP z$sa1VqW2bG?$^DOkwx$2f?8qEMFk&KoiR`(eN3^F$bPx~X-V;p+UK3g+v+m8fb>pX z)}D@#XaMUiR@{$@M=h}wU&nuV2#o0(>U~;vbBN8Ta>m5D7 zrx0KHj?%Z716F+o#q`?9U4B8q{qU$IytR#)4V9a#DIz8kBqisENE;MKxKbv`2b=n%tv|o_bsuev? zqIZs7ayjTtO18T$Lv`_RjdCJWIy>##>$;V@zMZUFH)K8(ppIEnKT=Bm z#{R<4ka(Qo^;ZU2Ta%{d_uyTuiqRt1_@PKb0>SfD6GoLLPNzl7gN2)J=f_Y_XqP2d zVS=?ac6M=2j7f0$7=ag>n@V-%hx8ooKo%o*vyZb=SjQWaHB+i_G+2;sX?5o88*z1T zQ^)@1C#W+UOp-kl^(DKuk4ZmW^iGp@5xlP6?!i>1+l{7pVn9_aP-Jqz^QyUTH18z9U# zRF`fshJM(1Rf{b;GE{x<72`jn9s z4IOEP;+?=uoEYXfIeBFhyc+yy9a7uZ>g z#Iv^0<`D|?Qxk2&PYHk##fmG^NrpJ?H>-=K^Vk)_cIi@i65 zg>bU;uq4BZgwxixZj~xUQ^cx-!2&}F6Mg+#QWZTjiH?Lt>N1* zk6L9!ad4wf50|(P2y~9?IVV8JU|0nab%3Ms%^xagC zh;KD;ns~^vGwkMp9~4Q; z`uff^Pjd!@3PYb!b563#4?w``L zZH>1Lfxm@gZyA}kcqii__=upUuQl$n5<^sLDt$TU9h>9ty`$htTxOK`7-g7f@8qDM zy~4O{%v(!V*cz6n3iYCxSy|s`hHPOtQ#2mS2yPMo_m?yR<~t+J1NOT6?}J`8q(~Li z`v#F}h$Nro?UN_(`aPg?zMtSo-jW)G|5TCC3Wa);LUIO#O(47oM zp0*d$Q1os0Ww)uzuFC_PqFj7u1!R6PvBbxij;Y}rqXiyLleKn)gv0e{ADATQ+Z$N! zf}1BpRWA;d1qx9gCwJdUt;&>6knf!Stjm^pBw&D*l?9CTzPJtHO!zRn{Y2pN^X+iQ z&qu*rXtf#-)OrLfcn6-el{)Zfl|AUi3=}5NqPKpC{xnu zoVLqiFUelF+pO!x7)S9Ep|1dP-=jbqt%|%y;QGpl6sf;3xf&_P+M)pu+oDdG#~H~p zACv|w(p;Zn*`US-m7crRi)-VVGbwZ2|HgRv$ zVb*d0I`-Vt65}ngYbX&y=76fC3aypaR6Px9q4By~dd5v)ndA~1fOeDU?afUrI#Axm z_!>ZsAIH>ohInwc?Qn}dd3!FbSafSxMqqwCQ1Q^5ruhY3WP=^W-E7yg*V8;T2=QiO&U>RMKDB;}aiQI<)!P@M5mpThI95yKSnw z`3hA77`Eew&Xm-`7WloXMts3Dx(99N?&Nu8xD59;4x?Eie!|-DH=YVUdp^7U!R3|4 zv2g}!b3a<40_UsrVo1~b0?!_Ow2Hdu7f;#KakQ}7p5ld&?vZ$R30(Ts>GRt*FCM7G zd^dbD#_6HJxL*P68oSsY^Sd2YmV7hcCtXbqd+piotydPO;76NFDJ=0FKw&ox=lChP43d!gf=#k zY9K8~-n4Seu)`W(K5um~hrx^u-V8B~=a5-A1{TnXFK1t;2OKty$C1_&!0s(GBoDUd zp%)O-2AV9C>Z>M6>)0X)MC~Onl3bwQbzVz!rdnhbN=VTOmUN{{tu^WC#vYePOaxX^ zbT%f+Wtj=J>RpXSSlG=A)-2qTeIquhmTmX^rFd(%-M8Whxzrn66J!mjgnRtbpS*K8 zF`}v!CeLEdi7APWuGHyN@Im^Gw?`@tQ>4VoF=}|P6S+SDry6BYk%~`dj9;5n zPt@7CG2q{%TWgQ6`qqZWRh-F=ht=2j%lmagg>TXYF}blp_k%2tdI+meoz<7RNqC#A zIz?O%ZcjalT|$%td>LKDokJ$H&@Bb;vHMR4F-<5C39($kE)C+3z-E z8wH)+MR7Np6}IIbgD&H?(Q_C+Lp3F}-y^I(+$LEHm&(|%v*7U9{Vubn1`ve2494Ghg{I_s<~vPx(lnd zrQ?2N>a$Q8c!OM|;_FS%YD#JJE;)w*%A=Vo@RiFh^A&RPX8Ln#pJMmVyW#5$8B5uz z%1C&f_WPT>m&R_h*1Q_UIuBz(x8CW`Z)`fTc<2P3XcwF~d{qGya-&lCiEu=VhVXpc zXQaR12`0AfOvLi0 zVf7u3{+i19?7Kz!a;;Aqtz>ratcN8v36zrudb(|)S@r3O2V!18`r?fNcxM($+O(T~ z!UV{yr_r}~QV1f@D81Az-@}o;ektg~@lG#?Zd_$`!6;0_5vw}%9L^<~IR8ujVg8ae zDMYb>7KOk!M<#aO51B*fiBUYqZ1#G85yc{7FP3M_;)6F7-6!Rb3D1@aFKU;oTK&Zu zDFr^vJWP5108Y9vK^kMtF0YzjMOhFd$)RzO*Y<>TUxM=oM!!f}Z_=i@V$t%$& zNh3{xQb|kpJiFktTj~ZsL~dhv8-(14Q)C!V^$g&9;oOUWqGX-_DAYZ5)$@}R6FHLV zMAoRzPBk4SS2pR^gMOl&x}v6x^9>$F z$LBoZ;;5BgMuP72t1njbUSNqeTFv1qRv6s5WquEVri&oW%HJaqJ`xS@rSd!Uc)9*} zufKi$=;nGOlMC*f2d%Eom-P^4kspL?7$s}@4RT1fT=+gD^F{*Tt$iFOlJB-M<7zB~ z4^K#o3XC2|81(1p#^#b-;3UUjsEXMHk3nl4+iaueaRY7a51F5p`d6Qgg`S3i6O~3W z5y#yJjWK$J-yfmQY+@ojl+hXmztC0{Lg*`a0u^Dce&;QCD+U$dwenTlr%)f#Mtj!OZDtE+|J zIcx;4nApF=PzD&U~iZ>*KwQ{@U1v`$Mw$m!2H7 zRFU(l#!SC962X@PYgmu!HGbsz_$S*X@;XH`FNf|hA8*WpZ`yb5NR!CRsQtj%aU*MZ z5q17qq8CoNP`)Uwa2sV=Us9IgF*DDI;1JVOG(6EF1(JvpjFGkS=fT+O*FLK6|&?%+1@@_pVz zUWX+i#@AVLn5HvpP4k_kZU&!_;C2>fM|*)CbrQ#9mj&_j&U5qhwRCo-GPEx26)D2( zxN)X%hXUPq5QJ)(k}Aa&6{L_-K5<_Rd8M0Y<`3Lhx*i;4m(XklpEVtb?#dB^SZJUY|T7C{z8CzV*nvbuxh(DgFe1B`IAgMEM z=%Z+c+M*NYkH*X!CAZ1kCW5@!f^Wca9o3B{oli>f9ylIPP}!MG@jDO0J!E8%%`PLu z^vedqVRjE=gSUa^R_%vP`sr<2mpe3B;sg+u7jro29yNZ* zQ`WmGY?3EdwkhtW-xDpo2Q}{S+Y2Rz>ssg-#aKs*Cl5_LhIc6WM5a+VF`)<9QA{ zn|Fc|KW0l*OyjWWrXMI~Va1q47EyCWypWGYVlwTMZP^*G-jfYNJyKasR%Ohbe}9$; zUo|0~l{f#`RpNf7P5I{R!+RU9OO5l|V*4ngdwri49T_Ys02&m-1kO>wLg?tvxjbf04iWh)#n-2W*v%_+V_ZUcx)*;68mWyHY$C7 z@6A~*52#UK^|5gYpMbtXQm3~s#kd&xDP9X z?ME9b!YA6Q!%q!%zykpAe@i$1mp1(WRW>0k5IxCG|8tC-um_DUc-V!!`by zB{ z&nbC14k6!Bw>95b27u_i0={a$07(G&ss$tdYx>K+Hb0OH*5`utKk3&#Kai!y3zP&n z0t@3KpnCuSegCGv?*ET`4QL1DKeeFgvj;)3jp9b0jvxR5F+o7gKZy^_|3caz52L@9 zUGq6A&w{@h@^5<7)u2pD$=7*_}^ zzF-^vJ@J9%FoM;Cxdu0oslW_mgY6zDPW=fQ=JxUudhH*UU-3WTfJlD!$rbZo@US-Q zfVvO=6aL=ZVEhtW#TAJ5OK|)M0C^x?wc%=3|6ofx*2>zj#>wNBpgcF8IYZ3M+ZK;P|=5 zo(IFJ10z1*ir3-+AR1f%M4bc3P>+0^<*kqhEcX z&IM#Cu>#p@JV2VZFpy!Z4b-K@0W$-=z`?=c?-*W>m)`*d{X!4-c7gIh2OveB1xQn5 z1#&d`fFc`tpdrc=SR5OKLHjBHmzZBg{^#abN5JI%h`%}34!^=xZ>7jg0Vpt#0P^*O zKza$}sBr>c4a9-cASa-wstmflxe0(#{f+tBKg=7<$1m4cJZyiyrv)f9kp=R#z<7Yz zzp8<;P-X)%-Y@|f>U_XRT^URkfVKTPUG4r84=ej4{?-Hs{Q3afl}c9=px8NhbmCzA`etL%K+7`Z-8tR9wrm&1JTZ>>Sc+Nz-wZ9jme;b!P$YYDV9Cje+pbpmF3>#p(t zRQ#*%UwD|z-{7|c@jI+E_&EW!ApVYwIN%(F0qVgF!}MV}EdAzxF_)JY(94U9pJM=& z`&Ir+7^ZtoS5+_`c78ZMKKfmLW%=jkO6Ge17Z1z-h`%Y;_6mQUw=GZ?Lpg!Jd|&0i9Ko=_=Ta1zPPdj)Bk1TuZGXT>047$D0psu1@wLUJ#YO7oxkY) zG5-B|?)anmt}Ao(SI3^;{RkTS%^x2eK?kP#0gqZ=z`e~E@a*shT*2H5q6N5Ry#>N6 zT!GBS1faU30+^Z@1Gd&TfTN=$7@n)Xy8?E_{|x`^U?;TF!yKshGzaFIOMl71+Il4q z%V0d1V0C}Q-&5#|KV9LrveI7&b9SWw>ikwdJvoK0t*!!JIx_(8jzGYx)gSO|^#eTH z0s!}hcYuBF8^H0~YrsBF3UDdb2R!TifzsY`;PmhWI6XVP!UJo|#>P5Go51$Y4lq47 z0u0nu13lH1z-a#0+X%7PNgY5vxrP2;?D$)WRi?solI%mM4#S;i`jRBVC7p{&i z7vTB!aDN>r_^|@Ki`)kM6L)}sgiXLdYzlZ6Fbp_N6OG-~K`N zkMYm(6~Z5FFP^>pdEIx_=Bwae2jT4W6uP;w2A!Q91NtUAfV_@8AfY1#i0VlKf;uCB zfUXF@uPqb^X#W6&^hALa2}HKX1AXIt;CTcJoShy(dk0qlP1kim!t4N$u|5K%3>N`u zwGKd1wF{6j+X6&Q4}he+E#P2p?Kkji46vsB5&yyd0R|}31{3^?&Q-r(H{pth)gSKc zL3h?ypsP#MKu3QAP}*Gt6bya`vIg^jq~0tbu|Es=G6Dgjd%gf|)9t|i<_55~x&(x0 zYyc`Q2Y{;60ifb^2q@cb0`dlZfTG3R)xMm~5#SNI1uQOteE`CMZNLPB1Iz!2AFRK^ z50m{*1T*s2bbNFSoE+=}Cwp7K(%d*OFw_dP57hyUqt!t9P$`hp3jykftAMSQ381rg z5wH(i2kavc0h53OK+AIU zVSv|L_0if13M0YY+umE)SF9MC7OF(`5GSJ$+0<`t60lmYUz{JcJFg3dlEHAHs*LC|p zjl;bEm+=SX{$>1JY5&x}X0Q74HxFZAZMbswGY1>^)Adz3$iwEzM~A!6tUl>e9U1M2@Was?k|50v|r{>Wd8{!!-I(7(w1 z^82eE-2Wqfu>NZNgZVF8S1fFIb#4||8XJP{?{5EY3(PJ|08G~|u6F;w;$eRNh#z$P z*W)Kl{wj4Aqys71qQGY*4&a-kCNSD~H7~oW`_%+k{(no??ta_-tG(;Gzr_#gT#X-C zNMSk?4aLxOaD5G;&Uv+d1zX=tSK$Ks%f7;@|0VvQ{;%}AQ^0~h@-RCvnQMFh$tJA+ zzrqg_1QT>|q!%bKkpQy6wHnyk;V;TjVFxnRxPb+*{z~xA>o&i!!5A=Isr+Pr@jur{ zPmYhF6YcfTwYixq8F2Ggd05%s;s<4}jz4|1<-k`{8E~zM16+sWzl!};e$8hpFab3o zwlJG8C;wLjvjMLB0QIpx;G<=9z!xJ~U}NPU*B4z?468F*dz&uis5Z`pwYQwd;wW)x=kI-GwVC%x;jZg9@;?LT5S}fHD_D zpvu(gy6KC7{Go3FK9P`TnbaMNs$mn<yA8)N)jsMZ2WH8>=Kx3c_&=~aZ zsm|9HXo(F34tBTy0Y41ab^2pDY^Amzk_^Vl@~V#;16;s(fN{BwQ!Tg#I9Y#nJ^BB% zZ{Up z1Xo<*#ttfrzyoDp9*aCyd590$WQWHl4+NB7&$-|CJ-)|#fTo#DJJUOJzPp|6-fz42 zoO2Z$6!V*2|9#`tJyp*KcKlhA#KYvdEX{g|>oy=pY+OOWr;-7oA6M zM8Pr!`<|{$a+uE=0tbEi)vdWvZid`w z*Vf{c3zz79{8E;=p7I&v2d451|F5;v+|DNW8IAcT0s20&oZ0%+=%2L}Y@6Mb_0{Z( ztgE6;rGwr08S1Abo_mo$oHgq$uJ`ezKllIq$C?M8<$l#XbDigz1Wj-J*j5s}>#z`0CRG0jp%7^076a`2~Hw#8(X`*Dmx8So@J94S4Po z!YhRL2YZ7{@Y%FHZu27Hs}cJ-id~u5U m^3h9eZ;@Z>Rip=cD)wmQJ33=d@)_sv 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 literal 154542 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%0DVxwnt02J{j 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%0DTgBK#vI% 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 diff --git a/models/plugin_images/installer.bmp b/models/plugin_images/installer.bmp index bfe70635868b80dd155a84c65739110f117e5d04..2e6b5860f5f4418760089d312094b459b5160c4e 100644 GIT binary patch literal 154542 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 literal 154542 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%0DVxwnt02J{j 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%0DTgBK#vI% zqm}<1lM>G7)A+HOa6_DM4O=zq6hhfA9NLV2b3Oi6LY_39DP@^3w$=%7?tz3k`GlWC zK}wi+W1KK2r3ZbK8_<97_D{fC>Al_CO(l6*j@sTGuJGJ<|i5g!S}sFg52R z(;m>Zi2K3*=?@C-nIi6n$GM&OMGcR0GZd`x-0)%MK9=DhF09|40(9^Kg@W~tmFPEs z<{k)-Q=1p*Zs_?E2mc-5CR)`y8>(kY0_QIYkCOr5L2&0y=5J%FJVqEn2 z3&olnYFU^dD!0{!(;gnDI-_0_)`z$82k@TjjXl>7$~+{KWC;m(6tl7vwoJc-$JHXY zyRKBPnK#Wofwcm^&OS9Gj_lEjKIeecRIQhmiq<7@h{w0D4DS5#hJIDvKpygk@h81` z{Vdz`_20s*eVZPkoS!l+KyY)sFdF!}kM?a9p5DEn(K0d(dY zv%*xkubU6nyzK-jxI4p_Jz<*p#s+JY8_<_IcA)pfb>^8B^^$7xTEp*9IS;{F>(T0G z*G5bR_m6-3Aq!&_^77o)X3>qZy;~~L>c)NGd}E1gu%7YYk?G?|=7QBJUDt)jU5m{c zUvs#4JS91C{VyN?j#`Xo&aa-yHGib=o&N+f=6n4+PA_BVbTYb;w*{@`9R3Dj`^Kf` z@nLvBxrUo$r8ec*;z^g*&*yp4dx`5#&-BN)E`w>q&Vq$*O6UGpMYeKA zurBLNnR>dRsV7o8;aog7_e_yG?9L3+cPbmTWzMefugvcPU=ovE0&e9g|*HFcLg=aBkak*kL_sn z@x(Q7hl%(SSl`~aKCw&=X-a;+L~%+ile!bUNlN~fk4TBcvamLlN3+F#!K6Z~E3S2@ zXA01~+YQ#-KXCUuFW(ZCFQ1w*LVhg&C~wO;)0HKzo1STjYnw2oXG$_(GY9>a@68!^ zk*rQF+&ua2Yu{5oH+O?IM{Z*-jCIA=&74kD5HrB7B-{42m5q`KrUM7qv8_>yb-1bDboHv_qP0y6~mb;YNx#^kaHW;yI zD#6Chpf(we@;c`h{l-HQJMS}|+og?*E#A^7chA(#vMfLo=;@gn?v&&T^EGqO?Yxaz zQQqm9!a)!vA=c9~Rj*tDF3)QJrb65_Q(~=m_IA^4(B0cj9=p1wBnph38#~uTzj0R2 zR0Vjuw;PQN0w4E`-`?HZ4bPUQ0B>M|owItTF3{RGif#7#8T9Lf_2lm!p8CMyDfbFp-R+rnW}nb( z96dc#Yc`@jb-H^i^jXqdaj>J`+$T-%jB|KgH^Uz_`&8BYmH|92{bnH-9&dZ5-91{( z;{<*@w*fpZxn4dGYZS9j9UhN<3y+I0{@B^4g7uX9LHrP)PZ8E24(<+f1vu~U6>H17 zng*Q-2Q4A|TdbAyi`UA9v^t)Q>U4WDgL{L{`iis&8Y_T0)k z1vziN0ixB(`LWjFbV)e(O!0rXpFunYn)F*&t9w0~3N#JLxTlsmc2MrDGmYnle&fQp z5BYgm-iMixx5Y}BBP=t) zDtFyow7L|#LfHCE9|U&w4B4BkUOYERcX!Q-R(H!xtrPW3jdi?qId2N*PS2FGml30; zXX?B?%K1hqH^uU2G2e(!g#zTR_99bjbt^aL@Xk5~L9AH_&`-})h%1dpyp3sZH#Jb= zX%Ft1+G6eYOc8IMZk@?01_%M6B*=q&sgU^2F(Byk{a1qJ|dgpTmIPoqLcbJ>X z73?n1WW03sn<-njf%AD1xCFedbul=J-PSs|Q<6K(-7{4)C8H6#4IzUdvkNFn4Zrs%~?drEsIjP&Sjh2coT!1tAR(hrsXiDsEkxRe1XDX+~JyTtX zxV27!bLpAda}(&EkudJwo7cFfKqJHJ0@;>cdB#}>?(L?}!;-Z=Xm2+C_G>FH?F#9B|C*9p(9o#{j z3q?BOT=9NytJNjiJ6avMr$4A{AAhyMy`$A_t-EKMz;}R)->ubk9;DTqo~iS^yjDHa zu2Y~_o>3^TQ~12+8y)7-Z}!}po@w#i+%pAnnd()mgF6pftq#T4o~e?&o94(i9UgR~ zC{xMC!&a-0G~Xyf7pITv^RODt2G)w@XO+NfGB0smcznutlh`YdE0(3-T#_q|$A>BT zyyLl%%sv00S}Z+NH&f!dwLQ~}!AQ1+$K|Qk*{8=lvrqD2>g>}{>l8Y(Pf71iEGnISlNwpY9cb$TIrrNYq4vcub$T`Nd+ecX%Xpct8H!NgJB`P4;|$zY zzbVYqGX-~hZbiU@GMU_oU16SwmTekpZ8RH#jcY6d!kmMUcf4yrJtj<}-&A_L0FMci zp$PO_cbx)hyy=-rzbS!Bzo|5ne)H!ka8dHP_ja>jqa-h$8xITa+I(9zcnx!zFvW8- z`gNT`r)R2?yy=;?VeWB9d2SwxP&37rOJ|*eR+tLwr@wt*B0zt8|I>GJJ}~JXfj;rB zVR@W;pv&XvH$5yoE;di?Haw4mzIta0`BBY2QL3kB+Tn5JdduUQUH5ErcwA=_dVnsm zFS*{XQxNFE-A1eK1p9C3c6n~Y%syqEg3j4bz((t+cw9Gt_5~_Djt?{X&$T225b|x$ zlwh|H!}!xsvrk38sduK?r>18L*83;ix&P_AK%5I>9o)GAi1+V+IKi$+AE4DMzZb2J zep8qit?n=%Cx2#mt?qd5XmxNO#e5_2p1@a8R(qydr=UBbv!)ot72qSTQvh)VI6#B@ zkX|d$%W4~9zA@IiOE!CMF3=SCojqZs-@5A*>YgyB^78p93UhmIy70`KlDv&Lvbyz~ zi?#YW8e;Rhx0}Lzgmnr@^0L}gpee2u#o&AQ{-?jKSe^uNgRc_1uojQs)8X+H|$K!X7(w~lmf06BC$oM){krcq2ck;6R$kp^h}ZH%{m1yKtsP3 z&kZ&Yk2m6tK+XcRPT=$SR!6_JT3!0BYITn}sp1}@XFAk;qp>dYjooz$U9B$t zri`r|?$qrSrUTX~h_aQy+jR=PTHS4wMyo5p3)V{M!^}6za-jakes-fmWIz^$yV!^CaSk?Yp#UWgR0 z6|DD7xC6L#)&*%?0q%S*$|k^_*a>r)Eed>xc{E$2-yHb&0=EjZcy3PMO6>OBbWT2( z?>cnPRLwGbFEZHeu~V95C_XE2T0J)<_I90u!(7DebqX1ZP{?=IDM-H&-RQSeYE93S3wi9i2a2^m`17!8 zfksNVek*&sHQmx$rj>OH>6s2#ry#7K`qsV)cXHnQtuV2icf2E**_HX@J9XjKx(It96}J$t)3&r7He!Q;KNPtbZ#&(t@7^jo)Q zI*`YQo_$J@9v%;&W4!QCSOf6{fg7xYyRlZhgZRFu?kHNFhl91cIlZgj68O>T1ir(( zYITLV^jp#D>8VYo}215tyAdqOk1tq@_V#;?U@Sb&YmzutGhsJ zw7Ssu4UhuOL~l+Xa!;7DPGQ8o-DJ3pdQTW@bt(4rOrzDo-BL}0E&8qQ?WQo7erxx3 zlU%oG?`m~`E?7TxhZDF$Tj2UgVy`@|#4ZL`XuB4ZU@Q8(cy94jd%Se>IQk7_$=*#Y zcY3CU$2|j6XP;=IxfKql$5RE?^i1WZmURjw_R=#g%@i0sJyUybF3CGQ9zyTd;H?i+ zcwG2WdbjILYtK~9o2sxud$bPX@v?Qm&^#_7hhWnplqB{t`y?u-hceRMZl#%G2ZoZo zbW3B!NVdgu%Y{5Qo~aC8d#2{`n!~?E@a`>n`%cBO5_rL#i-2w_H`b^%QF-*6v^x4t zl#SOa`$gyIH`U#Pd!`EQwr5&Ax7sspv9^j6ad&&BisjU%1$7A zWwG@eR30O}sl`gqv}kp=%v6R~++)JHF6;D6N7Cw~@losvBkmUNF=2}529GOs%Vjlv z57RTvhA_gq^v*iAn}Bu>*XT>CGvh9;4%U00_=XesH}*d9b*FLVb0v0%dD1&);3k1v zzX81h+=*SKnf04IH_&GEW!nv0zW;| yY#m^^o{*RMR(qypodWAj-7`&r=EB_8diOKv?YkCh-940It-B;IVZLbzSpPqX=`Xhc diff --git a/panda/src/configfiles/pandaIcon.ico b/panda/src/configfiles/pandaIcon.ico index cd6f45759f696b1207a83fd88424c00773ce78e3..3a2c1c33d4b3fecfc9b7a8bf0c2230cc3ce774f1 100644 GIT binary patch literal 147323 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 literal 287934 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Û~10lZYE#7;82<<;Zfzb)MJexmKhp!-)& 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 Date: Thu, 3 Jan 2019 14:46:44 +0100 Subject: [PATCH 419/424] readme: host thirdparty zip on panda3d.org --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 43feb9b19c..a9de69e51b 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on building them from source. -http://rdb.name/thirdparty-vc14-x64.7z -http://rdb.name/thirdparty-vc14.7z +https://www.panda3d.org/download/panda3d-1.10.0/panda3d-1.10.0-tools-win64.zip +https://www.panda3d.org/download/panda3d-1.10.0/panda3d-1.10.0-tools-win32.zip After acquiring these dependencies, you may simply build Panda3D from the command prompt using the following command. (Change `14.1` to `14` if you are From 9ff536432d2650ec88815e74c4d91a987b1098f0 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 3 Jan 2019 15:03:53 +0100 Subject: [PATCH 420/424] makepackage: fix locating Panda3D on sys.path on macOS w/ Py 3.7 Also moves panda3d.dist-info directory to /Developer/Panda3D/ so that deleting /Developer/Panda3D makes it possible to install panda3d via pip again. Fixes #502 --- makepanda/makepackage.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index 0747169df5..daf0877544 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -531,6 +531,10 @@ def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs): if os.path.isdir(outputdir+"/Pmw"): oscmd("cp -R %s/Pmw dstroot/pythoncode/Developer/Panda3D/Pmw" % outputdir) + # Copy over panda3d.dist-info directory. + if os.path.isdir(outputdir + "/panda3d.dist-info"): + oscmd("cp -R %s/panda3d.dist-info dstroot/pythoncode/Developer/Panda3D/panda3d.dist-info" % (outputdir)) + for base in os.listdir(outputdir+"/panda3d"): if base.endswith('.py'): libname = "dstroot/pythoncode/Developer/Panda3D/panda3d/" + base @@ -554,9 +558,12 @@ def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs): oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver)) WriteFile("dstroot/pybindings%s/Library/Python/%s/site-packages/Panda3D.pth" % (pyver, pyver), "/Developer/Panda3D") - # Copy over panda3d.dist-info directory. - if os.path.isdir(outputdir + "/panda3d.dist-info"): - oscmd("cp -R %s/panda3d.dist-info dstroot/pybindings%s/Library/Python/%s/site-packages/" % (outputdir, pyver, pyver)) + # Evidently not all Python 3.7 installations read the above path, so do this for now + # See GitHub #502 + if pyver == "3.7": + dir = "dstroot/pybindings%s/Library/Frameworks/Python.framework/Versions/%s/lib/python%s/site-packages" % (pyver, pyver, pyver) + oscmd("mkdir -p %s" % (dir)) + WriteFile("%s/Panda3D.pth" % (dir), "/Developer/Panda3D") if not PkgSkip("FFMPEG"): oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib") From 8cdeb9b60bd1d022140ee8e2d1ffb2e8c478869f Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 3 Jan 2019 15:34:30 +0100 Subject: [PATCH 421/424] device: fix a case issue in include statement --- panda/src/device/inputDeviceManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda/src/device/inputDeviceManager.h b/panda/src/device/inputDeviceManager.h index 9cfadf2845..7fd69b804e 100644 --- a/panda/src/device/inputDeviceManager.h +++ b/panda/src/device/inputDeviceManager.h @@ -20,7 +20,7 @@ #include "inputDeviceSet.h" #ifdef _WIN32 -#include "xinputDevice.h" +#include "xInputDevice.h" class WinRawInputDevice; #endif From 984f83d94b8dddae5df93bdeadb4775ae0b93a6f Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 3 Jan 2019 15:36:15 +0100 Subject: [PATCH 422/424] directbase: fix a stray pandac.PandaModules --- direct/src/directbase/ThreeUpStart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/directbase/ThreeUpStart.py b/direct/src/directbase/ThreeUpStart.py index b0a78fcef9..87158c937a 100644 --- a/direct/src/directbase/ThreeUpStart.py +++ b/direct/src/directbase/ThreeUpStart.py @@ -1,7 +1,7 @@ print('ThreeUpStart: Starting up environment.') -from pandac.PandaModules import * +from panda3d.core import * from direct.showbase.PythonUtil import * from direct.showbase import ThreeUpShow From 4d6b7e96c9d4fce747f623a99bf6b536516fdea8 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 3 Jan 2019 15:51:49 +0100 Subject: [PATCH 423/424] directscripts: change doxygen reference color scheme --- direct/src/directscripts/Doxyfile.cxx | 6 +++--- direct/src/directscripts/Doxyfile.python | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/direct/src/directscripts/Doxyfile.cxx b/direct/src/directscripts/Doxyfile.cxx index 7aec9a9d3d..6c81025960 100644 --- a/direct/src/directscripts/Doxyfile.cxx +++ b/direct/src/directscripts/Doxyfile.cxx @@ -1124,7 +1124,7 @@ HTML_EXTRA_FILES = # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_HUE = 228 +HTML_COLORSTYLE_HUE = 251 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A @@ -1132,7 +1132,7 @@ HTML_COLORSTYLE_HUE = 228 # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_SAT = 99 +HTML_COLORSTYLE_SAT = 150 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 @@ -1143,7 +1143,7 @@ HTML_COLORSTYLE_SAT = 99 # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_COLORSTYLE_GAMMA = 56 +HTML_COLORSTYLE_GAMMA = 39 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this diff --git a/direct/src/directscripts/Doxyfile.python b/direct/src/directscripts/Doxyfile.python index 5b2e73ff77..2119f7b321 100644 --- a/direct/src/directscripts/Doxyfile.python +++ b/direct/src/directscripts/Doxyfile.python @@ -864,13 +864,13 @@ HTML_STYLESHEET = $(DOXYGEN_HTML_STYLESHEET) # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. -HTML_COLORSTYLE_HUE = 228 +HTML_COLORSTYLE_HUE = 251 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. -HTML_COLORSTYLE_SAT = 99 +HTML_COLORSTYLE_SAT = 150 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below @@ -879,7 +879,7 @@ HTML_COLORSTYLE_SAT = 99 # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. -HTML_COLORSTYLE_GAMMA = 56 +HTML_COLORSTYLE_GAMMA = 39 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting From 7e562b0eb3ef5aa407b16d768cc49c8112828ef5 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 3 Jan 2019 15:53:11 +0100 Subject: [PATCH 424/424] device: remove unused variables on Windows --- panda/src/device/winRawInputDevice.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/panda/src/device/winRawInputDevice.h b/panda/src/device/winRawInputDevice.h index 8c880604be..ce4fe9a19c 100644 --- a/panda/src/device/winRawInputDevice.h +++ b/panda/src/device/winRawInputDevice.h @@ -42,10 +42,8 @@ private: private: const std::string _path; HANDLE _handle; - DWORD _size; void *_preparsed; ULONG _max_data_count; - ULONG _max_usage_count; // Indexed by report ID pvector _report_buttons;