From 5b8dc9a366a65cafbfc2a2aa6bc616a266b61355 Mon Sep 17 00:00:00 2001 From: Herve Date: Thu, 13 May 2021 22:56:59 +0200 Subject: [PATCH] init project --- .gitignore | 49 +- README.md | 11 +- build.xml | 146 ++ natives/jinput-dx8_64.dll | Bin 0 -> 134144 bytes natives/jinput-raw_64.dll | Bin 0 -> 132608 bytes natives/jinput-wintab.dll | Bin 0 -> 158208 bytes natives/libjinput-linux64.so | Bin 0 -> 18432 bytes natives/libjinput-osx.jnilib | Bin 0 -> 23620 bytes nbproject/build-impl.xml | 1474 +++++++++++++++++ nbproject/configs/nativeStart.properties | 2 + nbproject/genfiles.properties | 8 + nbproject/project.properties | 84 + nbproject/project.xml | 21 + .../java/games/input/AbstractComponent.java | 145 ++ .../java/games/input/AbstractController.java | 233 +++ src/core/net/java/games/input/Component.java | 782 +++++++++ src/core/net/java/games/input/Controller.java | 263 +++ .../games/input/ControllerEnvironment.java | 161 ++ .../net/java/games/input/ControllerEvent.java | 61 + .../java/games/input/ControllerListener.java | 55 + .../input/DefaultControllerEnvironment.java | 214 +++ src/core/net/java/games/input/Event.java | 76 + src/core/net/java/games/input/EventQueue.java | 95 ++ src/core/net/java/games/input/Keyboard.java | 69 + src/core/net/java/games/input/Mouse.java | 183 ++ .../java/games/input/PluginClassLoader.java | 169 ++ src/core/net/java/games/input/Rumbler.java | 67 + src/core/net/java/games/input/Version.java | 101 ++ .../net/java/games/input/jinput.properties | 2 + src/core/net/java/games/input/package.html | 14 + src/jutils/net/java/games/util/Version.java | 104 ++ src/jutils/net/java/games/util/package.html | 7 + .../net/java/games/util/plugins/Plugin.java | 60 + .../java/games/util/plugins/PluginLoader.java | 169 ++ .../net/java/games/util/plugins/Plugins.java | 252 +++ .../net/java/games/util/plugins/package.html | 7 + src/manifest.mf | 3 + src/native/common/util.c | 118 ++ src/native/common/util.h | 53 + src/native/linux/build.xml | 45 + src/native/linux/getDefinitions | 60 + .../net_java_games_input_LinuxEventDevice.c | 243 +++ ...net_java_games_input_LinuxJoystickDevice.c | 158 ++ src/native/osx/build.xml | 52 + src/native/osx/macosxutil.c | 197 +++ src/native/osx/macosxutil.h | 48 + .../osx/net_java_games_input_OSXHIDDevice.c | 118 ++ ...et_java_games_input_OSXHIDDeviceIterator.c | 143 ++ .../osx/net_java_games_input_OSXHIDQueue.c | 135 ++ src/native/windows/build.xml | 81 + src/native/windows/dx8/dxversion.h | 13 + .../dx8/net_java_games_input_IDirectInput.c | 103 ++ .../net_java_games_input_IDirectInputDevice.c | 502 ++++++ .../net_java_games_input_IDirectInputEffect.c | 42 + .../net_java_games_input_DummyWindow.c | 71 + .../raw/net_java_games_input_RawDevice.c | 70 + ...va_games_input_RawInputEnvironmentPlugin.c | 166 ++ .../net_java_games_input_RawInputEventQueue.c | 171 ++ src/native/windows/raw/rawwinver.h | 14 + src/native/windows/winutil.c | 21 + src/native/windows/winutil.h | 17 + src/native/wintab/WinTabUtils.c | 167 ++ src/native/wintab/WinTabUtils.h | 92 + src/native/wintab/build.xml | 65 + .../net_java_games_input_WinTabContext.c | 106 ++ .../net_java_games_input_WinTabDevice.c | 107 ++ src/overview.html | 13 + .../games/input/AWTEnvironmentPlugin.java | 51 + .../awt/net/java/games/input/AWTKeyMap.java | 289 ++++ .../awt/net/java/games/input/AWTKeyboard.java | 170 ++ .../awt/net/java/games/input/AWTMouse.java | 221 +++ .../net/java/games/input/LinuxAbsInfo.java | 69 + .../games/input/LinuxAbstractController.java | 74 + .../java/games/input/LinuxAxisDescriptor.java | 62 + .../games/input/LinuxCombinedController.java | 32 + .../net/java/games/input/LinuxComponent.java | 78 + .../net/java/games/input/LinuxConstantFF.java | 42 + .../java/games/input/LinuxControllers.java | 81 + .../net/java/games/input/LinuxDevice.java | 39 + .../net/java/games/input/LinuxDeviceTask.java | 62 + .../java/games/input/LinuxDeviceThread.java | 91 + .../games/input/LinuxEnvironmentPlugin.java | 477 ++++++ .../net/java/games/input/LinuxEvent.java | 53 + .../java/games/input/LinuxEventComponent.java | 115 ++ .../java/games/input/LinuxEventDevice.java | 367 ++++ .../games/input/LinuxForceFeedbackEffect.java | 110 ++ .../net/java/games/input/LinuxInputID.java | 52 + .../LinuxJoystickAbstractController.java | 70 + .../java/games/input/LinuxJoystickAxis.java | 76 + .../java/games/input/LinuxJoystickButton.java | 65 + .../java/games/input/LinuxJoystickDevice.java | 238 +++ .../java/games/input/LinuxJoystickEvent.java | 59 + .../java/games/input/LinuxJoystickPOV.java | 54 + .../net/java/games/input/LinuxKeyboard.java | 68 + .../net/java/games/input/LinuxMouse.java | 68 + .../java/games/input/LinuxNativeTypesMap.java | 848 ++++++++++ .../linux/net/java/games/input/LinuxPOV.java | 95 ++ .../net/java/games/input/LinuxRumbleFF.java | 54 + .../java/games/input/NativeDefinitions.java | 634 +++++++ .../osx/net/java/games/input/ButtonUsage.java | 135 ++ .../osx/net/java/games/input/ElementType.java | 74 + .../java/games/input/GenericDesktopUsage.java | 136 ++ .../net/java/games/input/KeyboardUsage.java | 242 +++ .../games/input/OSXAbstractController.java | 74 + .../net/java/games/input/OSXComponent.java | 70 + .../net/java/games/input/OSXControllers.java | 64 + .../games/input/OSXEnvironmentPlugin.java | 261 +++ .../osx/net/java/games/input/OSXEvent.java | 73 + .../net/java/games/input/OSXHIDDevice.java | 294 ++++ .../games/input/OSXHIDDeviceIterator.java | 65 + .../net/java/games/input/OSXHIDElement.java | 143 ++ .../osx/net/java/games/input/OSXHIDQueue.java | 135 ++ .../osx/net/java/games/input/OSXKeyboard.java | 68 + .../osx/net/java/games/input/OSXMouse.java | 68 + .../osx/net/java/games/input/Usage.java | 40 + .../osx/net/java/games/input/UsagePage.java | 108 ++ .../osx/net/java/games/input/UsagePair.java | 76 + .../games/input/DIAbstractController.java | 72 + .../net/java/games/input/DIComponent.java | 74 + .../net/java/games/input/DIControllers.java | 78 + .../net/java/games/input/DIDeviceObject.java | 211 +++ .../java/games/input/DIDeviceObjectData.java | 73 + .../net/java/games/input/DIEffectInfo.java | 83 + .../net/java/games/input/DIIdentifierMap.java | 546 ++++++ .../net/java/games/input/DIKeyboard.java | 66 + .../windows/net/java/games/input/DIMouse.java | 67 + .../net/java/games/input/DataQueue.java | 121 ++ .../DirectAndRawInputEnvironmentPlugin.java | 94 ++ .../input/DirectInputEnvironmentPlugin.java | 240 +++ .../net/java/games/input/DummyWindow.java | 64 + .../net/java/games/input/IDirectInput.java | 100 ++ .../java/games/input/IDirectInputDevice.java | 541 ++++++ .../java/games/input/IDirectInputEffect.java | 122 ++ .../net/java/games/input/RawDevice.java | 310 ++++ .../net/java/games/input/RawDeviceInfo.java | 67 + .../net/java/games/input/RawHIDInfo.java | 80 + .../java/games/input/RawIdentifierMap.java | 553 +++++++ .../input/RawInputEnvironmentPlugin.java | 205 +++ .../java/games/input/RawInputEventQueue.java | 150 ++ .../net/java/games/input/RawKeyboard.java | 124 ++ .../java/games/input/RawKeyboardEvent.java | 79 + .../net/java/games/input/RawKeyboardInfo.java | 81 + .../net/java/games/input/RawMouse.java | 214 +++ .../net/java/games/input/RawMouseEvent.java | 108 ++ .../net/java/games/input/RawMouseInfo.java | 88 + .../net/java/games/input/SetupAPIDevice.java | 63 + .../games/input/WinTabButtonComponent.java | 52 + .../net/java/games/input/WinTabComponent.java | 195 +++ .../net/java/games/input/WinTabContext.java | 80 + .../games/input/WinTabCursorComponent.java | 51 + .../net/java/games/input/WinTabDevice.java | 160 ++ .../games/input/WinTabEnvironmentPlugin.java | 136 ++ .../net/java/games/input/WinTabPacket.java | 36 + .../samples/net/java/games/TestSample.java | 58 + .../DefaultControllerEnvironmentTest.java | 72 + test/net/java/games/input/VersionTest.java | 82 + 156 files changed, 20840 insertions(+), 24 deletions(-) create mode 100644 build.xml create mode 100644 natives/jinput-dx8_64.dll create mode 100644 natives/jinput-raw_64.dll create mode 100644 natives/jinput-wintab.dll create mode 100644 natives/libjinput-linux64.so create mode 100644 natives/libjinput-osx.jnilib create mode 100644 nbproject/build-impl.xml create mode 100644 nbproject/configs/nativeStart.properties create mode 100644 nbproject/genfiles.properties create mode 100644 nbproject/project.properties create mode 100644 nbproject/project.xml create mode 100644 src/core/net/java/games/input/AbstractComponent.java create mode 100644 src/core/net/java/games/input/AbstractController.java create mode 100644 src/core/net/java/games/input/Component.java create mode 100644 src/core/net/java/games/input/Controller.java create mode 100644 src/core/net/java/games/input/ControllerEnvironment.java create mode 100644 src/core/net/java/games/input/ControllerEvent.java create mode 100644 src/core/net/java/games/input/ControllerListener.java create mode 100644 src/core/net/java/games/input/DefaultControllerEnvironment.java create mode 100644 src/core/net/java/games/input/Event.java create mode 100644 src/core/net/java/games/input/EventQueue.java create mode 100644 src/core/net/java/games/input/Keyboard.java create mode 100644 src/core/net/java/games/input/Mouse.java create mode 100644 src/core/net/java/games/input/PluginClassLoader.java create mode 100644 src/core/net/java/games/input/Rumbler.java create mode 100644 src/core/net/java/games/input/Version.java create mode 100644 src/core/net/java/games/input/jinput.properties create mode 100644 src/core/net/java/games/input/package.html create mode 100644 src/jutils/net/java/games/util/Version.java create mode 100644 src/jutils/net/java/games/util/package.html create mode 100644 src/jutils/net/java/games/util/plugins/Plugin.java create mode 100644 src/jutils/net/java/games/util/plugins/PluginLoader.java create mode 100644 src/jutils/net/java/games/util/plugins/Plugins.java create mode 100644 src/jutils/net/java/games/util/plugins/package.html create mode 100644 src/manifest.mf create mode 100644 src/native/common/util.c create mode 100644 src/native/common/util.h create mode 100644 src/native/linux/build.xml create mode 100644 src/native/linux/getDefinitions create mode 100644 src/native/linux/net_java_games_input_LinuxEventDevice.c create mode 100644 src/native/linux/net_java_games_input_LinuxJoystickDevice.c create mode 100644 src/native/osx/build.xml create mode 100644 src/native/osx/macosxutil.c create mode 100644 src/native/osx/macosxutil.h create mode 100644 src/native/osx/net_java_games_input_OSXHIDDevice.c create mode 100644 src/native/osx/net_java_games_input_OSXHIDDeviceIterator.c create mode 100644 src/native/osx/net_java_games_input_OSXHIDQueue.c create mode 100644 src/native/windows/build.xml create mode 100644 src/native/windows/dx8/dxversion.h create mode 100644 src/native/windows/dx8/net_java_games_input_IDirectInput.c create mode 100644 src/native/windows/dx8/net_java_games_input_IDirectInputDevice.c create mode 100644 src/native/windows/dx8/net_java_games_input_IDirectInputEffect.c create mode 100644 src/native/windows/net_java_games_input_DummyWindow.c create mode 100644 src/native/windows/raw/net_java_games_input_RawDevice.c create mode 100644 src/native/windows/raw/net_java_games_input_RawInputEnvironmentPlugin.c create mode 100644 src/native/windows/raw/net_java_games_input_RawInputEventQueue.c create mode 100644 src/native/windows/raw/rawwinver.h create mode 100644 src/native/windows/winutil.c create mode 100644 src/native/windows/winutil.h create mode 100644 src/native/wintab/WinTabUtils.c create mode 100644 src/native/wintab/WinTabUtils.h create mode 100644 src/native/wintab/build.xml create mode 100644 src/native/wintab/net_java_games_input_WinTabContext.c create mode 100644 src/native/wintab/net_java_games_input_WinTabDevice.c create mode 100644 src/overview.html create mode 100644 src/plugins/awt/net/java/games/input/AWTEnvironmentPlugin.java create mode 100644 src/plugins/awt/net/java/games/input/AWTKeyMap.java create mode 100644 src/plugins/awt/net/java/games/input/AWTKeyboard.java create mode 100644 src/plugins/awt/net/java/games/input/AWTMouse.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxAbsInfo.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxAbstractController.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxAxisDescriptor.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxCombinedController.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxComponent.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxConstantFF.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxControllers.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxDevice.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxDeviceTask.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxDeviceThread.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxEnvironmentPlugin.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxEvent.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxEventComponent.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxEventDevice.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxForceFeedbackEffect.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxInputID.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxJoystickAbstractController.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxJoystickAxis.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxJoystickButton.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxJoystickDevice.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxJoystickEvent.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxJoystickPOV.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxKeyboard.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxMouse.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxNativeTypesMap.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxPOV.java create mode 100644 src/plugins/linux/net/java/games/input/LinuxRumbleFF.java create mode 100644 src/plugins/linux/net/java/games/input/NativeDefinitions.java create mode 100644 src/plugins/osx/net/java/games/input/ButtonUsage.java create mode 100644 src/plugins/osx/net/java/games/input/ElementType.java create mode 100644 src/plugins/osx/net/java/games/input/GenericDesktopUsage.java create mode 100644 src/plugins/osx/net/java/games/input/KeyboardUsage.java create mode 100644 src/plugins/osx/net/java/games/input/OSXAbstractController.java create mode 100644 src/plugins/osx/net/java/games/input/OSXComponent.java create mode 100644 src/plugins/osx/net/java/games/input/OSXControllers.java create mode 100644 src/plugins/osx/net/java/games/input/OSXEnvironmentPlugin.java create mode 100644 src/plugins/osx/net/java/games/input/OSXEvent.java create mode 100644 src/plugins/osx/net/java/games/input/OSXHIDDevice.java create mode 100644 src/plugins/osx/net/java/games/input/OSXHIDDeviceIterator.java create mode 100644 src/plugins/osx/net/java/games/input/OSXHIDElement.java create mode 100644 src/plugins/osx/net/java/games/input/OSXHIDQueue.java create mode 100644 src/plugins/osx/net/java/games/input/OSXKeyboard.java create mode 100644 src/plugins/osx/net/java/games/input/OSXMouse.java create mode 100644 src/plugins/osx/net/java/games/input/Usage.java create mode 100644 src/plugins/osx/net/java/games/input/UsagePage.java create mode 100644 src/plugins/osx/net/java/games/input/UsagePair.java create mode 100644 src/plugins/windows/net/java/games/input/DIAbstractController.java create mode 100644 src/plugins/windows/net/java/games/input/DIComponent.java create mode 100644 src/plugins/windows/net/java/games/input/DIControllers.java create mode 100644 src/plugins/windows/net/java/games/input/DIDeviceObject.java create mode 100644 src/plugins/windows/net/java/games/input/DIDeviceObjectData.java create mode 100644 src/plugins/windows/net/java/games/input/DIEffectInfo.java create mode 100644 src/plugins/windows/net/java/games/input/DIIdentifierMap.java create mode 100644 src/plugins/windows/net/java/games/input/DIKeyboard.java create mode 100644 src/plugins/windows/net/java/games/input/DIMouse.java create mode 100644 src/plugins/windows/net/java/games/input/DataQueue.java create mode 100644 src/plugins/windows/net/java/games/input/DirectAndRawInputEnvironmentPlugin.java create mode 100644 src/plugins/windows/net/java/games/input/DirectInputEnvironmentPlugin.java create mode 100644 src/plugins/windows/net/java/games/input/DummyWindow.java create mode 100644 src/plugins/windows/net/java/games/input/IDirectInput.java create mode 100644 src/plugins/windows/net/java/games/input/IDirectInputDevice.java create mode 100644 src/plugins/windows/net/java/games/input/IDirectInputEffect.java create mode 100644 src/plugins/windows/net/java/games/input/RawDevice.java create mode 100644 src/plugins/windows/net/java/games/input/RawDeviceInfo.java create mode 100644 src/plugins/windows/net/java/games/input/RawHIDInfo.java create mode 100644 src/plugins/windows/net/java/games/input/RawIdentifierMap.java create mode 100644 src/plugins/windows/net/java/games/input/RawInputEnvironmentPlugin.java create mode 100644 src/plugins/windows/net/java/games/input/RawInputEventQueue.java create mode 100644 src/plugins/windows/net/java/games/input/RawKeyboard.java create mode 100644 src/plugins/windows/net/java/games/input/RawKeyboardEvent.java create mode 100644 src/plugins/windows/net/java/games/input/RawKeyboardInfo.java create mode 100644 src/plugins/windows/net/java/games/input/RawMouse.java create mode 100644 src/plugins/windows/net/java/games/input/RawMouseEvent.java create mode 100644 src/plugins/windows/net/java/games/input/RawMouseInfo.java create mode 100644 src/plugins/windows/net/java/games/input/SetupAPIDevice.java create mode 100644 src/plugins/wintab/net/java/games/input/WinTabButtonComponent.java create mode 100644 src/plugins/wintab/net/java/games/input/WinTabComponent.java create mode 100644 src/plugins/wintab/net/java/games/input/WinTabContext.java create mode 100644 src/plugins/wintab/net/java/games/input/WinTabCursorComponent.java create mode 100644 src/plugins/wintab/net/java/games/input/WinTabDevice.java create mode 100644 src/plugins/wintab/net/java/games/input/WinTabEnvironmentPlugin.java create mode 100644 src/plugins/wintab/net/java/games/input/WinTabPacket.java create mode 100644 src/samples/samples/net/java/games/TestSample.java create mode 100644 test/net/java/games/input/DefaultControllerEnvironmentTest.java create mode 100644 test/net/java/games/input/VersionTest.java diff --git a/.gitignore b/.gitignore index a1c2a23..0885ee6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,26 @@ -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +/build/ +/dist/ +/nbproject/private/ \ No newline at end of file diff --git a/README.md b/README.md index d0fbcd4..4ae4739 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # jinput2 -a fork of the jinput project +This project is a fork of the https://github.com/jinput/jinput project, whoch had no changed since 2018 + +# Changes +The project contains the full Java content necessary to build the jar file. It also distribute the native files. + +The other changes are: + + +# License +Licensed under BSD License, copyright is attributed in each source file committed. \ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..3fb5ac7 --- /dev/null +++ b/build.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + Builds, tests, and runs the project jInput. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/natives/jinput-dx8_64.dll b/natives/jinput-dx8_64.dll new file mode 100644 index 0000000000000000000000000000000000000000..732718c028e3027298280549aedf4c3e2ffa180c GIT binary patch literal 134144 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 ZT#-Pu@y8=@`t)y4{a7OQEjz@g9< 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 R|5ed*QriD~{&Fqwe*tomys!WO literal 0 HcmV?d00001 diff --git a/natives/jinput-wintab.dll b/natives/jinput-wintab.dll new file mode 100644 index 0000000000000000000000000000000000000000..3ac18eb22ab9a86b78685997007b834d43bd631d GIT binary patch literal 158208 zcmeFa3wRVo);B(r%p?OO^h7cmx#%FH27?+2>cm8yCNrdmOdtqoRFt@Zh{|dtJrD#T zp%cxd9Az(FR^4}b-J9$B?y|eCT)ZU&a{-lrtAOk)xbAAj(KX&ez)Sysr@Ch{fw=4Q ze&6>z|NsB_cu05G?bNALr%s(Zb*iTPx{a1pi^XEYpRQXhTk)oUKJon54~Nxa8GGjT zv6g2>y*hcTHT3G_1(&&{9t2)Uwr=+!g=xOq#w%M%Xjy;x%)`yI{@Bcvqfz@tP9xE?83`-qY7y zFVZg+`N~#bde-BP`b>iD)6RRk=SCHL4ZM}L-eeLPRU z+17eNu6pJNIL=C+yn-NJw#lI9w6|x$I;;)m#Moh)| zDWa$)KgCjP6gQp?DV8=@QpBbdOD-y{$WF0LOo~47vli(VfvB0X(P{yxZ~b?Hy0l(J z>XCXAWool(ow*%K)YXS5g>~z$ zUSvYCcjg(zrud`2xk%qj>Cw!8f|L0zKeAZ3GpRVse1ft^U9TfqcSQ{l4<&)9K;nx? z1c*Zy0mL~3;#J71I0h_pu>qnD$+~N{0irqy#5{obEfN8udpbb8`$K?e9R^~e0ph=q zth>?;5V=Vpt^$ZhkO&ZeE&zy41fp~ph=UcPA&(+ickRR&04=_tIFOKNF&`ivL?S?J zAP_eY2-`3a4;dhC79bi85Kkq6SO5?kW(W{>;Kj!ih<0%O5H02#Aet^FEUqv>tV#m0 z5Fj2vBCyCO5c_`s5bK74a2g=y2@qf2EgDjs1mbFdSbK>8aXRww9}|e8VIW?eOUbVB z0>oAWgf$7oHAuYWQUT%|K;rWV#301RP(xY`5Vv`$Az=eVC&iiMtOSUAkcfu7Pgsm4 z5Pu#9qSOH46(FV?AXpNJYXL$l6d-yDi`^RmV#P2JHUmWSWrW4YYXvPTl0aMs5DiEK z7C#^mEd=7iVIbP)P_kr_UIs?RdB#8!~SBBaJEC;h|d&^~mdWYLu< z`5%;#W!{Sl3l1sGJ!+=zx>Zy-sg96{Oj>KaqQPWSa4!*Et2A&*0eEbur(5x*xVgrIx=HA>@$-2!#{v=S66 z*r`OCJex(#K{O*gS>E~xg`%$I2A!hR+6DJhYajO<5lPQ`INqGJ4<)kJ1F5{glOJNc zl`g->jYx~Ubv>mqhobdZd3uw@(%iO9$3IDFZ*E&RMu~o%-9OMoO6@HzB zLXA5Kzs1)>Zrg9UF~yQ-loDA)?NL2WY6NKt3OiWPqr1ikFs^JM8j2c?DH!N7J}Lwr z-Ad#cPn-B0)Lp4yEpX-P8tIPAUlW@}?QVhwDlH?-suX7LLgS@t+K4!tP!0{NB2aiQ zfjC57)l*Atudk8TH+dQmg^&9@U}~g2<9QNq-Sq_cp$0Wcx~p_G)#IuM*w`F}xo*aX zybZZF5va-l1`?oE6!cI~p@$EDUi{_bFBgAq-8GZ=3-HG4uA@+T0IvdI{H#r3yZJ|> zX*hD4pOjj0Uv+j?5*5Rn-39=-z^TM8sUs?_P{kLBEj4OEh&;LKX3cNk)%BMlcQt z=lA&V40$SuU6(~FmW29t|B4BUQ^hhtZ($%Q6oDw6wSOI z(V?h)xk%kFG8~ZgAGV++l!5IIQBP0;VxL5;^y~pX>!_|9UFKgUy6kcTIOxnT8)dP? zBvcqQ;`mgD#R66+*hg$VBbY)xgw7sqlq?!5X^EFCHA{Y+j^5u#lyOIAr-DBeG$HEp zQ8JNVcbyLo1g(~%A8Ki!R{i}yS9RAGf`cI{=!GG= zWeidBaStu1FjR@Ki*LN)B-&FKP=5(lV*~>|)VKY(t|!>%m3aTi3*?3J)iU){KZu5Y z`XRBRA(@UKCP-vG2>p$kSGzqMb?UDB0W`!?(MFYK6$qc|ep&S3ICRz1n5@uMjUuU+ zQmHS0*bfaL#3;j`j10>}26P$v?#QqVe>5`8P0GNBW%!Me;S$OKnz}{L?;e)rO(V+$ zGYiHA)if;2AtQ^!%#uGs7SdbLmap-mio7Fa`EMi3r)C!42wCV4~QNp=n1hd_NGCBDvYW?!9a+8qcGk) zAl-j8F;rCbtgVsG#C(B&X!B1K;2y>ozsrDB&UX6QoX-_#`|*yg1)HFba`v*HjZGl6 z%!ox8`EAG?!T@lC{*<7Ac|?N4Ovu$Y4vYWFjL$zIKEsUno)GUe<9#Q@51R4H3Gvxx ze8mazIc9w23Goxn_^L#Fi0uimPKCXJ6n$eYNGj`{N_6G4p!Ts)jUo*sxj)tG|CRj+A(W* zlpLKmrtb*VsU1r(gL{o&su|p21Z`&UPe#yg27h4$)52*4DmE5y(xv8BibXiyk}BP` z0~%3to7D6slE5id+iB&FR%5X2MtBK6iGynKuE1NxdjV_mG#I%m@kyME*hQd+zVQ*z z0Zb`xBQOv^G|5APnL|$q4e;V4NR8dVFcHQv@#-$Gz(l25yxSqP2qxyA$q+*UHZXx7|GX$y8Pj8Fx;~uiAfipJY z6;~WgkuCa*G!yqV5bd?-u@9g^n~YJVFq%wr5F@h#=46V-y0?SHEYY$hz)pAFG?PS` z-{TSUIAV3z9ExJ6LOOs1Zb%4Y`Xu=Py_yXcF7mk7RZ3_2EZ`^)70zvJCk;`sVd`$( zl_4Nt-d;eO2j<@RlroG%eF0(L0I&bnWj!5n|ISV$;JI5TWQIPc0ND zC{1;kxe|&~Qu`g$baYhZ645S56d+XTo387wX4z<)TeJ-%cGV%K;1C*6xvo$;lkj&_ z_6ie!)Mz36L&zpQi+JB*5?~w*U+a|J*JOzzfNLU~XRGM)i_F7!4 zp`>L%8i=?T-#F5E9=_Bt@DY5WLbGGXft9||4S7&(RcC}qaM5(gvjw7<-0d?7i>Pb9 zfiGtu5Vb(adCcw0hSxlD`~Q7?zYt)lzI|AfqrOzLz6t-jzG15be zps2kEKtt$jwAiGl(3L>XGeri-#bkl9rtx7goNvC+UHfTK#O&(`S5qfK zOppc{DEbM1<5Ng^h|+u-06x;}askp3>}sb4m%McomB-q`jpZ0u!URl=6(7b*XOrhJ zk}YwWjfsMCV?69Zr_cm};7EN<7*eHJk1h8&Nhst9?gLGN>{yWXE9`^h?uKHGu|{)0 zKO0zKM1uVD$xI|BFTM#=)$pzc+k>#nN!`I(Nc8*(TBcdQSPx=QithMyyOZcijMg9o z8ud)rDFzV%QUC~}2x3s)Mmfd03lc3ZEgMK$(ufnXA(7vd0K;DxZ9vXy03zma1Dd|k zMWJd90h-T8czANLuv1E_C1U-PkjoUoA;@O+6PuOmma5T$F1t< zA-wrIjNh1@&&5l6wv9NqBM3&vyN@`j9P{Uc?IM^%EM)Kq`be;#d^L$Eg?$=gpA_`) ze*#Do=35fyJPf+E9%#X8&bP&?T^@i`w@A$|BdN=NF#>>Q;cdv&)TYkmQ?L%h@7k-vy0gDKzs6*2U^w zVQU4)x#b%>wxO*S?gs3QL}#Ll7%{%qW6VzaH0&bt_3nuM1=4hvfq4ZnyxQeOglE{# z*HUZGL<0XFmgd^oQxI?A4-ZD*9NVt0+2{+(RvzPqakwg zsdr-8y6f<*SY;j{ZG{x6XAldjvHc(!-}JSv4<-F1Iq4!yD@Ky0Tf<%erTL*?9~E;U zEfH7*7^vE6RIQGUC+MykR7`4)rl*D>iOo8vCDo};MaeE?Bcw+~E3G6nW7+Y%x@$@T z_gGrNAxdSC6pK9dU}v9vK>KFVgHapk@q2A3x(PT27vY_^>Sn(-&DA;XQD zE_A+Er-J7Tpg8kgG}=gAgh_uqdW~j!9 zwRYJRd{I|+*{?Fcwt`tK%57~lxMowyE}NA`%}_kc{M!YHkA!UQcqAsq%s1kZD0uBH z7=P(DV!sc%XqSBjo<`f2*cnnPfUY| ztfD|}0gQbB3p=3zw+PlpFq+Wu#N4laXw+6GlK(`t73ef$p~@=o81!kgzQUpUmQj6G zL-k=j7$Q?;a(&ZLOz+b4l(l~lH5$sCKxU!BJ~U*sJ>z_k@ww?w96tXid1z-*M>tG% zm**&vK#uOZSYQ>PkW+V^i4e>*7t!|^<2zdJHo~7RL0Z6#qE4m#h+8SZcR|3Z6tpYC zC`_CA$`k|&hU!uze>?iHWg7M9B1qAry$Ci^5JN*4oP>Y_GoiPvWIoZ)ehL+d@Fy4m zeGMeXqxLWGE#_kW+dk?}hmF4{IMI<}_APjkkAZkb$4y?UFx%Prg2xm`%&ru5tJwu@ z{fhL5%;y0p{LkseoVVE;J8JSV-vb_|5y?kGILA)s)c}uaqgc#Bs%?5(ES(?TV6kkr ziVBJMG5K8%Uc}`0paCxqD-TFFwS}qBDSYfbB9P9fA~yD6QhA<^Bz_8_%}6Q_W;#(H zGn6rsQGJ~Dp&|KB)xNZbvxMMO*sIWUV22l7xxiCtsI4b!UVYj1pe51iT{IDGb%N;6 z&@jX3==+fdT+Ui6pxgP^YbY!2yBh8MWwR0Pdj#X%kbly%_FtoLbI)@$9wmQAi~V7Q z`7S6S{0pHJ2nuBL!6re0QJ_~LipM@^1z2;ZL$ufAS6{>BIe=hc2c_m;0VWIw(!EdN zgHJaQ&TT#v_5f*}WXKsn7?hw!q*J|PMOeKBM86%j`Elo^Sho34nC;MxT$$IcXx+N< zdbd)r1H66&e_V2kcmJo7v%Fu*vj2tz&q--nng5CSc%Gk%@2t#U;@xS#6;CVr4!m7~ z7Wr=I*ak$gZ;gk+3s5)Ox0YXrjfFzHb+4|6S1Z-_8Tf!Q^GPqgGMtTt^oU3mnTCWX_MoDi5=eX!O!{!s-2L%l0^QtAi4d?A zN~D>pAw)!^xr-*|-{uy=^98gbUIO|N?S)i8SNGw)fQEtY`V26kT+xgMcr-yr1O3vE zK@b>?sM1!d6t($u*D8F1g)=~TM^s7WF9}=UbjD)11rLi)LPRip{{BjRo zu~VSI5&_b5*R-1ut#vq*qQfit484U^7NZ~HW%c((*+IOL%k~o;M<}}kTugO4X?>Vu z>Xpudo-X@q=NMXd0kn-5@hv346Mok2XZspIsg}$Ajh`;PUA=|H0tJT}zmUV~GC%7d z^MTy>=`FV}4UXxP8~blpmoE3iR1qz)7WBxCpOh}WJuE#D7}FcEc>-hhqFx*O1U=fiI4wM#*;SO#x*c8iYJAY}h}dt!mpstn zR>x`G?k@Xm1i>6gQ1C_F?WHBla&Vir(`jhAue?&wR%rj+2b>;kqaiQ;30_KC?b-sb}WTaC`3aAM?L%G?9b?0@TF1^?r7P4R!#x>JJp`3t(K% zkl8LJwTJn4+mxcg6_fCR0XkThjUvuM|88e^G`=$Mm7zLheMg|Ecf~6*>j@O?4PQZd z03!nh!kz$TzG<;Z(OK3%Smj8m;-_+GXL;&QS?>j+D5ZB|00P+!SECt1uOeG0)`8dO z2ia|2*hBICZephZGP`J(dOBu}QA$)ErR4RtIxfoqN{Zgr)D!-+psiy<8^%kAIak1* zeLds^P#UL@$)lUM-A%%=tPM)*ALlGLQpQA-512Ohp;ix82Rie znC^nCqoYRri|={J{&rSH-9MpU8YQtYI{*Kh((I5UxP&=~T0Af?eNoP_43u>V%P$2v{(j_%*rj+Hk z_7i4rUuv-&!e6@AV%d-P-|_c3{%i>U1@8>JIo@u(o%lO~?|%GQ@ZF1dF5WPDVWV|c zx2~74rpD!ng_SCZ|ExpS->KQCMCN@GnRl!vGVkk_d3pzhKab4&3iHmqqZEPnXOVed z(x-H$pJc|oPb2e=BmXx@bJAZ;q{7isF*=?u!W6F{-jrKI^nxZqDnNNbTRE+~mcSZM z1Dao>fHxa=Qd+!>Ssx@diP#)J@;PBpb=!Y*ddy*PM$N{d%uPg2QCMO2H?F0b(sg_g zx=YaxbxP8TBJv2bDMgBNMH-%tmFe5aDER7Bj3@9XIT2++8M5U=E@Rzc=a0e%7^4Gg z}eNKv5y+P7awkPfJ@81Clf1~9`* zNbvhO!UluVF6!XL&4%I7l=E>Lhj#gN0N2B00g9l6?plMTIm?`cST@N2MuSh=r56lP zYpV=+tUq=&%lz^T=+>iw59%o0UXQZT*%5v|s$rRjkw!)~BtaGF@7qky==-iC#RTEJGLQU%;5WxS<4I|tS7JTh(;{)l0>%6BHP)NEg>b0e!)gn)b%nd z5R3DS$DncV+D>+NUL~qivvm7wNY*>B_FC~eRBEghK@L%y=nw-AoAe}d#U`QaR#A5B zqYGeg*EtPs)-xlBMXw(g^$Xqkgf(B%oQyf>SSWgN=T2QOXYT|V$Glv|?)zr*Ewn%3 zx?mDIK-;6sY)6poQwsKCsngo6yuM2*+AlRhT!S_C5$EjagUW3wFWMn^sC`iuHHJXd#J4sBN(ye2#| zTDcjSut^8vJqaC@PtXLbOf{l@i72xF*^OBE53~uWx~s^52V3hQH1HoC7-%3$CYG|n zdz%Diu`@8a!~?`*#7Mep*L)y?tP2(GWm~Z5L#g=!%y%HhL?_8O;w-wuRy`WXFpttPa9}AMk&Ig=>6VQEI@zIM9P*$uWjWy)! zdx8bW%kF(%WG0P^7Jg8>U>{Op%>f4|O|L`a9TN1JD$uhB`z1fe2Wa&Rym%_<`)HU% zBLs!XSXfPTF~5%jeF!R(uMPrCGm1gAg%pn3?=&E-AV}NDM7o<`WyA}QPXg2_042dG zCpZ`i1gFq|L+dPl=gDyPp{wICZx?Wq)+6jKAeDoX>=+s)5mXEog8C)aHvBAM>3yB; zAmI<0VYPAr^@|w!V$h?FC&(+A^#kyJz@QrJowQ~WRqVvqHb^52@B9i)hLu*p z9^+@7y6apM{FbZY;L$CxpVC4|)Ug(GLG<$6Jg|M#9;8jY891bkbj8PEgnw67QJLA#u{aw@6q zMayjK>_y8Q>(ZNbR7J*Z+Fxr1Gh&NPda%PVcjud}Cc0uKHP&wngohS3Z)8@V5h8hl zdHj=d(3&0Rm!SS~mhFLziJS@}yc$G>`W}HobmGQf>S9OHZMJp)D7wwD{z#C);|(TJ zu@2t^*%GQQ!OAd{idC6MG$)&6lA?wXj*%yCEvc=?qO`E8x|4$ccMgX4Z-NddxNQO!*Vj9u7lr$Qau;F zQ|<_+_K-xCSc$FBy6!A&@)gRq_2d0Tlh?ZZMK{^jOS0zG!>Mw_8J!7LK1JUV)Y`L) z-dynlC8*P72HwnQP^wE|*|N1LsR4*@v6s0V22}Jh1kPH}2s|ca&7uxjb7Nt97W_tI zZ}1G5)=9T9#}jzXT(`SJWZ}Td4x76CJuxmJBlm<@KQD%WXNxm1-+T1k{8#XGM5jMC z#CO01d{;|+SL;}p2HAT6vJVqMnymHcF`J($*!09edV@s$Ri|Or8N!(G6uU0KiR}zU z6tPzY-41=IIB0mGzYc0u-54jz5$t1e?hzP}C36grfGv717z`E`A3D$yBc~o8nqMon z*bSKm2udW2#|D6UQDtS_7=DC4nNo@Q1!`iMgYTh;NF|snVSG%MW5@XKMB)0%@WuS+ zqHslJ3~STtuwpx|j#k(>no3WPeFR500u9tibBQRTD;9U~AJ!7(g-%v2a$wjBwaLc4Lf!<;n;;QG z?7=}&8>9B_>)?3nM8rX{w3eKT6Uzk2>2ph2K_`OwQkLvS*oSaF!W9Vn5UxVFLUOh= zfX;}jLKHfuVWSbOm9ip~qC^@g1{JM=@)-mrpjm?}n}zia-&IO=vte)}Uu@RgPiP^& zUV65f%oa){&=O=bePSsN^H?u=wZnV5hgA7m@_hka7d8ahDPoPTbOkn&(yqIHkF~$T zqSRV)Z|FY)$~EpJYKXlAQ`VVo489;S1!R=q`A_(3pHsKZhxn+d;_V`EHTKymmhopq zpv)_rf4f)~?fj)yR28&g9~(J<4*p;-AlEn&dOQ_}#Y{ICzrxzFCXKHz5yC`z&vshB-Pd?; z0Kigv^mlsk@iG4P;_op2Cd{%}-1y7KpAUZr5Lb(L1O6=d+laqP{B6Qt8~(mPx>#Rz zNLxnNL>!ofMk{zBeEnSv;Pi!TKpP@`KM9iK^eqLm>9f)gD97FY+Rd*0RC}$MG3KWCJfU^bWbpZ$UDgYFxXHq(FxN2-tCVxxCSoDW2 z%Ut%uUMFFlp4(09e~PDtx-4al8L|aPO%H*-w7L%!Wj1D|SXQ3aw+}Hx{YT4xX=+fE zM?YsJSqd5uzfp4fvo=wJX*1Y_$jublMv-JaXhTdd#T=oS{_uYhNHQfzx$Uv>U^MO~ z`^JWcF(<^a3~JmNw=|qF+`f?|3tZwxhmVEPA#--r1_2Xtf(;e?$>7Znkke<8@_>IH z@Xu+=vJhi~4unUd9eHvC(obkd9?T$l!`gx3uw)XrHl!rC=cm%|2K#cc1Q7fP(*(7( zU(x!U)QW+2Nj(jNz~}>{q53Qbc4f-*I^qVIWztk@EE8Sq7k%NBrj8OY2+t!*V_$>O zN3|ei6(v+qg3qwgR3Oqvk@*zq25s{Z-K6`EJdtCfl6ShB!fiP@~$;5XqhV8ncZC)P||EO_lvj&W4aD!)QMAtlcwN zc2+QTpHlSJ3K&1z14Z2{I4O~#?_yu@3vUt1O=6D3x)!#A)wDo~ z;5gEJ2hH`6L$jfdD-K?Xof*th5s{ucH1R`uiNNF*wXYW`GAZv%#3#1$+LP=w2K>{% z4gMewF$_UPA4TmS(ZVJ|COJpnEvSCj-i>{aSoR!425jac3!jB7107a1b9)Z;Alt(q zxrC-T=u)Iy800m5d`R!5O_;dGTM=TBjJFoT(zwTEwz zwbePc@YOPt-Ev-=Hkh&s<923luu#qoXLhC1qSwe-VC0-8vmkPI!=HSWJE*PBw}emU zeIM$2m(NF;g$3LwCmgy!OxmZ6uOVuof7T0KbYa5Y5?$5~`{HJ5D!bukm|8I91`7U; z)w#cAX%|ePcy(io1S=+<4sjWZmVX?Aje=aGUPask@ZwB_`JGsx8lj^8b!YmaR3JOQ z7qR#r%C-R!F-(B3av!Z|`?YU$>Apiy2#V@#Qj{!cjaL$}q~;s(t*y_oNzJFDfvnVh zK9LWUlJ0dNB2;*DzSK5_N?qQ9a?)lImDBTEK6T3E&0;zE^5`fU2vbAglHATk#Pj579lLNR^jA zY!}x5TLbK?K;b#k{dSZMvd{T`C|tp69Og*v^cOC%+SCbr=TMYVy~t|ge)btW&16hYdb!T><=n5(N8-!!O#9Ho}IJsaqHOYlNvf<>) zMufkxIzzf=4hZLGfmKjL_Y2mheiG^)I6E5Hjeap0eElcrobugl;r^msl6D%=J6h|M z3s?3_55zzOxoG9*(gW`hIR-K8%B#-_r5=<gj-0*MaZ-?UeE4D4xcTi?S1uj}1GZD)3@sWos%Ij!*m*T5nMZ5FGaJ*|N*d_i9itf!XYy$Ua1 zFgmNB!d^tYPapXqSl6NT!nvt4&~4=_vGzdgK?A)#2&zZQcY_iej>Cq9G2-_w?UtH9 zMI%`%IR~G4`DE~>wBZP1Kxwx+mcK(j&edgZG{fjstRN>1lz41X({|*7C<#IU?*FIg z-{xNxEOBU@VRtyljMgX1?#Zswnm9dLLASzp@ZM{J1Gdq6#WRv zaFj1ShQ>u*PaMJwK;e0?JsbP)!f73cAQOTLi{{2cG`d0T*J88wZ?LXQO%AX%e-8u1 z&z|-i0U8S4z4X@Vofh?KazEI8B~35~_)Ey96jnM_U*i?!b4!k>-o`7gD#07=YZ6C; z&Bo_rY43;R&lSYR;8+fFUDR*%(nA;r+T9u0)cpm%{Fq>9(je!DDlwV@=4cAw1XHxh zJfaLU0op~g#(C&$bABpi=sqJ zKPQs=1oM%1OFkxFFe|N01^xRl-I|0Wvszbn#F*s;6*s(S`jLo984KU(Kks=Go_PXqr3eGG;RR;Mee2l!tpM53R)UJfu=H*7-dg$@F7kfFay zK$|~W-yKVppS9{;MOUnCA!VpSZWTsBNU)PE?ff*+BuGZ=g7hl-5z=j08`j-G7Q6OA z{cq5pS3AI2_C>S7{!&1uokgMZQuCn-to7sww`n(=?Z~hFkX)E^w;hy-r3b5P9ZKqS z9tABlZdHrn-V?sLaB+?*@ySNxV6B6r)&V7YjU&kBAh#!?ce+#<^rm00!SIex*RG8{!pHGkO5x$>&|| zl$xLcTME}W!j8fsDOyT_qP3%u4Risd{(;m67%m6Woa!`%*^gnGrm+U+iie6pZJMp= zrV53Ou;VzOGr%h9-DJ!0ZbQzd}V^;#AmG*KLA_MUJ0`wO!=+27r z>RC8?`5J#59UyD#vK>+XzT;oZ4X(~qezVao7OkQg0vVam0Qz1wt)i^dX65{+u1RUWj9Xpndb~J7vuH-GV78N zFy9!h9bCrT!i(u~DLtms<9s|e@^k4)HpJmmg3wlVKX}>gpZvTKwxP+m*&&2LH(JAY zze9T3GN^r!E&K(9U|)4B5kp2PY*htadmy9+D^*DNU@}`uYaP(fXq(8d)GCl* zge3MG5igX;kVoOgX+-n`R2b6lc_B<=^)LpzMd@QeLaXv2i-EUhG#f5qG`YUUFBip| z1Z1p`++cmvGC*fS}}itbTr~}L)b^~#p?@^)jh=E433G5 zcftV~!zDbu@e1Libe^^jsXiEn0_l$2fe1s*mz%1gLtB1A@Ar z6Qx}_=}Sk`dFR_; znfiRoItPCapSnV{Q0<_@`g4cj_}?Vk6XTK2R$t%2pRS|<_$zu+pTKZW>vCseKZ?#q zVLZcx0uhY0Vn1!S{Pw?5?;KP{r?v^V?Ngzd>KCc@NdJChqlN%_`O);_Dx4DFamw&dU5C) z1!#HgFDgR`7%M>Uxw$b~&kDG&@(uq$p|4T7wkE?8E=^tztZ-{<+!l2le-qC8=2AdV z!Bo$}rzct{ZJ;%Qu{t1y+MrGN4$via#Vv(%-7A-nSkmep7WEYVD8a+JDpWns5u$|w z9PrAqU4tXHO`bMP>8 zQC`R|S_l}qQQj=xYE*$mmANK(1k5e04C@TY1P` zaL8aD@KWKmn7@_kJ8k?wKwC^(X*6x^W}k_9^AJ$S{DTauZ2a!w8NAnG@)m?lZ%{`M z1T0QtnneCAK9BN(L90tK+xOFK|Ct#F4In@ofKv2{T2{E&hRmK3GE-a#IfGi8GlZRt z=qzU_uT6T^F;jXr+mg2n?&NwKoY6nR3~0mdWZ~wNrhrAY52QO(kC@!?X;n|hr;TE5 z>R7oc4Q}S?s$Co^U>z}gf{^H`#6eOJQUM`J6#@-U{x@V_CA%r9&X~X!c)WgenNPvK z5^LvwJBY#G%g4M$Q~=W>87cDC6ws5uj6_s`^AEs%Y0Cnrhyc`BC?x(oVW}#Ep(<{G zSp`Os^GWHsSizxBaGs>3&I7z)F24ZqVrL9koba_|G9l(wkaEbM&wcQ zv1}5!ZKRZ{d<*~We1mVvUVNhN+yEULrbw(s)eyB0^zf5=lA?Dt6niby@EB2G1oK#CTpb1j z81y0pU;!nMr%`+|%666@LbzxM>z|?XLs~=KHxHduNxAliuRs^Dx1`+6pNg+z($qj3 z;(vyaG_|}J3BM4Zfg=d^gZ%2GffT2jgKA*CM{Q_9CGhh3a1QJu0E;d}C3kc3>xtZe zLkvsv5s5Qa_!}hufGp^~VLJ#EPVWjFvG{&sg%Juy_wplb;Z9cGrn~Y`f()uv#$5($ zurPTSyPQJjr~OiKkPVRs#rEcC-q8T&A$!91F|ff5VuPALk0>_Wl>CZsp`kRA*F^Mb zj}s`p~%t}lE%xhw_HheMFI_o+vtxheFWQeOE=+f zGfcJ5gd)G(ijsJ>i5Dr@;M;nC^r5f8618Ptn=(4J3p&eld7A(z87`29S zR@4>FZ9Y_=3m>_aQ(||NBlA*f?rF_!tL^@lbLc(}nn#Md)Ea+_2TPkM)y6w-O4RK4 zF7Z^tGYi^^Py5d&k@_`Fmr%JWK#lP)K8!$Bc@|QRK+rIpxV|ZWwao_)|`triD{|Ec>I9T1}qJP(y z*SG!e@5|E9{%v1+fuY%#2d_8!vhdr-<$G9~B#+DYdjC)NC5GcB8ky$jLq|CY@Qvvlg8%FG&WDi*i1up=ip+t_~5)5Vb84i=*)!yOnX zGQ|7HfF@?ogc=3)fgB@EJJQH_UYWr24Okr#crM1e2B^0GBjr1w`Xx%9i0ZE?ZUj^x zK>W9(`bvOx%UWLw%uQOKRh^0j$tZP-tPN(WleBki^45=ata=`7tIoum z3e21`o9%7xF|aBw*cX*`IWp5gt40%e)o8~HR}MoA-dBqB;x2gU1htvUHx!I*5!gY2 z5AGcdL}pe{is=_{5`LlKBum0?FAo6&kc_Yc$>P|RgJzc`u!B>BBB&M`fogrz-4JS=#a%|5KgCvW*Ve*A%tG`8buO z#?rJgIutNE9FYb~`PXZoBW$3CCI8BP_+b=O4TrkAQ{mXs4Ke|lKuX0`EiFr9J^ zBoTbo@1#YPYKQw=%&A@e^*7*|aHiz6|M;I)3tZLGVsrS{P<53^x-e_db;}4~B(?|} zQ)Dh^(9LgLN`ehLQr9^7J;pa{B>nEmlyD{vg+@f6vor_Di=KftKID&3CSDnLr6Sa{%4O{_Hc@KIrkfP*8c+rHL* ztY-Pw&=(QsCcQZqYXWgwBi4{ON--baIjDTT6Ye`oBuW?vrCk_!>9LF+wSGM6{Vm}J z%xmEd_}hrUk3*3^KY{{yZ4xh9Rzo9)Y7S4%5SH3i{>YyO{Ex{628{gxDyQ#`mtyt^yBm`Ff>GQE*dnzawr!^8S`mZ^~S}9-zOTt zHV%rxmf~-9v|5|m)a=&Y)*bR?d(|uFF$GG!{e2&?1>BUj}0M_l=;8Lvl z>B_e;yzMHnGyjS7BGzCctv(DtrVe14-imS7VxVemb+iDvj@H!HT`lA2qZcO*J~24O zoSNhOVEFHw7cX6lF2f@G7@dO2;8!dp0>eUH>STH3pX$SU_eJnCV&~@2=AqA`SlU#8Q3J2ecwfIIa03H6@Zq zh$&)fUJmyGAeiv$PVg+P>Dj!Pb{{8B+L(`IZ%smtAT;#0I)0|EHNDDy0K*GXO1h-Q7k^ikpbv0=)=N4ECX9% zY5N821*Qu9P=%pF{|;nS3h&HO|E4$ABA2#}o+&uOj07@lM9=^70;?rbR}rb3)`zV{ z@JmTfVO_3tAC90{TFc<{Rx&LrA1S_Wl}q{^mtJhwxEt4j!9X@u`AH+&+A=4@ zNeh;?eSa3j-Ocx7>DKoo0u5{A^WZ1vCG*p6ahFpcZnFp3OWY9GC2pn+qE2N`?A%D5 zH+Hv~eMT61$xx2S=;of)4y`QTcJyF`u9leBS|S&_)!gDK>KVm(YIgDY>geM9@D<47 zhSY$nrZ3bJY5q#@|@8l&GpE^N+9Dz_lw;d)i4smH-9`vq}jF4%^{;m zZ2yp~1y2|kBxu`+6F|O$;D-0-C5p752pKw_CfdUGXHnldfgK(|iop)|O~b*s>I(c# zvxqZygtfLN*CO4AE1!rX*!+ep;Ec|a1r~!X(y@OAwkR9M7C$rD;sjn;a}qBIamvSGSetC{Oh~Z7 zUx(xZcGHA>JwYlQo|`DsfHK4fM~SKv%p(RpUmc?j z=7-lJk0Z8{a>i=$w8m~7bz6RHDLzwTi$@R>Wkfx229zp|Ml7qyZvP--U6Zq=hp;EahMSOLfdSxWOdK(D_U!9+u%vX<6fdpUu z5~2SEz9Jd+Kj16cG$avo2Mic}b~CU7gSC&eoD=wJAKaGujvzjnS>k-viFo3xok`_J zhO?N|}X&al(x zg$^J%#{;KS*1GCjSnI-XXRYs*C0Hv}iL;iu*8%$- zE(l%%T7ZwilLBQw1aAWEEU~~a(ALUwu}7Z3+iYK!zXeLE5zujy6|O7$7|dwO$c?2# zr`wIaBiVtCRh)YX%FvkoE%O#A3`9t&nN^sr+`U|3kzr&T=l9ObQPX7Imm51r))ZiH zCIyIeLDpd8#ql5bgc|`J+Px2xKDG_z7DW!?QcGyW+5Gw;oW7bR%w!<3*q0J}TCK8N zJS%+qfE7+amEgcGc^Y&G@EEQ&Yd}91&k3I^5{ryPXhRmBJ_Peg0kiK+q-v{+E#Y^I zgHqEUk*PRjc)%4ekeX-#SiDTVz4%u3#^UAbCB>?GPVp-B)Z)7~W1@W*sa-NTt62K> z8{ZfYmOk=bMLeO?_W~a1o16yk^6*$RQHjVoTY(;KVx?UQOGGa2qYE{WAH&De7sbQK zzicQ!aJ0nU5Sx^-*N_uxo+Va;4{B8G28GSbX-z3E2%pw)mvwp=H&M(?LE>lvh3aC( z#g~Yr8HprJ2S(E5X_dw9RTE$rYE!mbog0LM4Q@}`QI`$~qmV6`<~7>tnQ@x0J%Q%+ zK%dg%WM7m-_Kjd;lf5nqP*?VmH-SNR+;cWl8wBllqB3m|#HUWs20{Hz_}IP-4;&<} z*9LD@Z{EJ#2o-9BD(1PtRqAQlpy-Q_kpPTEUzq(O`r-{EUi1a^3(>Zw&#^I3a4 zx64gkYPz(&FFjJ0znz#LDnZQ2Uqqo;juy;!ZYPGvZ2|l=j1gQt?La2jRMH^;u+fdB zh!4(6Q}D&g;B!y|TT(^AM=gHORRrEf;4h%BG*J<`QJ)_hFE{N`)9E`eHkP&T<&@^2 zFl?(Ix6X&@@X03+<2IT&f_C5y*P!Wb(U~tjc^JC_0bpRm>^Y&+KdmCa;drHLZ#XW8 zQ+-=1eJyLJ&BF~NDQwLQC0du8x27T#{S!@Dd0!8-XM|yb!L27v0PzF>7_wgD-va=e&v)~kAh&jfW$gr30;L~0 zyVPM-d683Y6`T5%q=#0Ksbd+YI^i~I-1i6jp`mxi9fEx@9H#C!4TsoVCqet7$#~J* zkNBhOBL3*g9`K)ZZxi}SYC49eT+}Hwk;5JFAeha+@_cQuRJx}hY1-g?V@fP~S!xpc z7})R)uG(nJ`xjCs+`wJ$?~>*083)%Nq`nwn_pA(hMHC*7rhxy zm9~6Q6H(XVPUEf=RyQNBt|IR!@iBf9t1K6nrzlaO1NDp zjYI3k#zfd|z1tF-XmCC5*6T`eyb^j9`fF?&}0*G*W(nGJTDR9cI5VYMJu{xY27WRjZdKar}Z!Cvkio_Op_%-{X;# zA-2DkBy^nZw|-Z)?+QobY~M|6`ac!Zr$OI1K}>t0@PX|oLEsVFpEZo_PaVegA0@N> z_fKN`z;|N%k56EG!Pnoy_VV+@?s4Wnf$f(p2iw0n#P;9RMAj|`+e4Dq%?Obsud7g^ z&k0HX1&x5La+2i4U$lu*Asi@6aFiC2L!vyN(jEbqVzehj`RS%8Pe2fd(Z<!PN&a z)zR>hwL)-q$8gSm2En0f5B?o&{hx?ZG?`4XvOC0lgZLc=b#^&^EP_t#%4}Y-_Cbp3 zreAb8P5WS!dJ5F$Ox20y&$Q|%KgOdF+dL_T#yEL2u)dVBfIPR-NS6A9^Kv1df zr5?NMZTj&P+SkWSKUvXUa)6&LVLLXa$*b^Q3xdjCZvQzbA1T4M!M&6LTO*3(heRt; z$o)~6wS<2K$+#-AljAyQ-{7aXXc0lU#o2M}h~PbJ!25vWMunme2#134A@+cXr-MZr zZQ=*__JvY=f_phYIiwfMWP%Hcl}c|fEAi4afsHid{Dg7u2;0x6Vt+zt1@OuKMn#0hK z6G`2`I!3OMr*zgF9oCy?geTI9?&5j!;%nf_I$PCB7eGpSRz)}fDUA|*42^(<|c)o!+;gK2b%?iJ3}G&U3a zoh(>PzBdzTyJ-@6-e6|~5~z|G&y+xrK2WG<8KWk;20H4)Vn|`okk|4UV>Du;H#RzC zj6aM6T=Aj|D$%kE{)1v-8TA5~|E)3b|C&nVA^;GDAsUm$Xe@KL&Ks0loLwcj844V0 zxIaO%H1}ZS;>>QDVh9;w_cv|)@t-#$aY%w9S;i$vG-*riAb;=>QC19kNRA6btPI=g z*t7JtsBo}o?!n6BBC09Ktf6Y3gYWeUR(A@#N1JHWL@pY_OzcQD_rx(y!qSVeG@>7t zxlY6>CG7Sd1o6d>lF6;yAHB<_9kZ`rs^(x~2vK*;r$;G0F2_D#co7syx<*255RkS) zU+Ud>rl{A^9iLb7v`Z5!{$Ce}6+djtAm#B`C!MXg#9n%dXdBzA^`)ts3G#2L^M4h4 zv_Oyj93m!UUE{{u>`BOTbS|qMj9q(TteU1&BVMM&;*%2773n1;Od*QLCXJN*A`)!U zv$ccfw2R7cS!MIU>QTzkx3wN0R+}UT?;h1vA|`>@IHh{NS9|9u62MQDeDwkJBYcW& z;QYfh0r^{B6~DH)J6NRSs$=#hACIn%rQ_orxVQs}cAkz}CTVLbEOg^!nbJ_0k4kl7 zDNJJ4d_6vgL1Zt-79)Dr*bTsf#=K`x_{Jyv>Hg35;}sVt`tk5Zq93s%{r|UqymtTa zezcz4kME)TL_hupeV*vYDb$Z!NnbQNv!>7rKMk!!`G$J4CSv_Jy(#1$EidpRm$+r{ zIm5PbOtD@ImX`(F^Lhw!cvGCl|AcX58qpS@O4v49P%)cp3|tzq)6^?4VlSo9G@c$~ z>5+>OtDb{vAJ2MD47>^A@C$yw0RvB>45Mk_*<-KcjDn)Ytm=8KP z$x(GZn_ujg{4iCQDWrQ&B;9k8c04Zm^G}leXOQlLyLd87#$xq_V!`TOQV973NXS@D zJoVzCRQ`ucVEYzh3UW}Kb)-3Gh~}o8K1TFrcY1L@%8<0KG|*Hc*FqZilRkRr4Z4q_ z2b0ib@{a*3leT6ODvK4R`c0TvI!l2~6v8hSh-dhOwN`&o=h`=*NJdIWP-)|SJLa5&N!7mL<-~H-*RXm4 zM(9EHb|zQAISz*6JCy=lYoe~hR5+Gqni=rhfjNPnu_pmW9N$PpULQ6tbJ(TYQ7SR26J)i_%hOW zsVqMg`AIj&83i)g{OAWX1ClBLj;W2Sd+~M|_9fac&xcDHn&S7~xj<@ufY_ayB;DVH zPimITzAW_;Rw6aq`B<{-gU=3bh#1^G_i&OqO68=Q_1s{PBKJP6c#Ks{)G@FEAU&ra-tD*DPn0w-6_z{Ff zsJabgz@a43h0+%Iae?8D{l_c-HTNIS7Onbs8<4@&ejWD}wGkyE->LVX7g)l<%V;VX zmX4%r;@3*am$iv@roQdKD;BGt1gi%z{OHcb1lGp(qiijV1!QfM9=lS>Bi=ho?uSKP ziZrty2ZH%qzoR>Y(iAIfLU`7K{AmsKaI&#ki{ScEbQC`aH%5=8SPKl54jqAo+KK*M z0>{S)M?-j=H2LBLQ?R;Uph%L$By1eTi(<0{ihe>d#K6%&u`>h|%c2u6p|h|B0?l;A zniFK{A%SMPiRPtn8ZR0Envxz%^;<6`Jkfb0>jop78IIrR1v27<5-y-9uRbC!Z@>}a zGlh)e*Pc(*KM;(hvpgI^hwI#&sHY~j7PNxE7#c6QN6K^@zB9ofqPk%TpDh(f9<8rDOQ7=1WX51km7CF6Fn*I~0Y=XW(19fHGIeSaaI z!*9rB(Qz$22EsW7hYVjU*i3NzU_ebb=5yS1&VK+1@HyY^XZci`ju$r&0JfigZMN3D z7$=vU9P15{Ui8Jvsc4jy&jiwR=ie+I0uVTC8pP*UAmoqa_uY=3pz$;w`8xfV>abXT z6Fyxjiouy}yip>29(NkKV{RT+ECqbbAP{H774zamBfD z=L&=$j=^{I;nrWWzxI{xPS{~{a6J+I$`ef|%>EGkPsKq(T=|0-@nh0h!yGHvCvR<_ zEHtlyxcvK%6Vl>R8(dY;g|X}2?jxy78TidWNZxu1cA{arT7e(@F`}1Mp#3}*`wdvr zq|!uTO2yy%zF`57J@Ufp&owR1f@k&WM;nhdiZ zd@7RfS8DulnGpT4SV%Ic;zaKvp&{N+-jg z{i}2oHhvy?G7>4}d?V!yYKxJQZl-(#tK5sD&8B14!VhQixsQ<%8B;xZV&~(;xg4E? zd&j*47Rw95)dmy2?IyFr0;-=GovDm14uK+DB7TJa1G9gPhDEH>7W*JRY9cc3g>E++dPc|~0sR;f@H#X^0-Kq#7yCd+IUP+i z%Y0B~lz9RvnAqVoQ4=VfQ3@2bHV2_7-9w(Z3j0FQJ1J>ZcmQTk|Eh0f;Q@`E^F`{E zV07*`I5mGKLKmtoi0V2gIT{D`PHBskvV{K;V(*pby%*HJI%nngg4#Ey)K$vcplhLf zyUO!kGqI@xo@NirXf043JJg6PO;k@o%G{_8DjjaA*v)gz=q>2kzOMk0>JT?HQgYqG z;NE|lHRZj=bu;r61N!D6Lc9Zwhxq3+fC=VddZNnKai-ULRXqe zcOxlwYNCbwTr-}277)PxZcB7-rkV1Xkun`yjV94P3mUCmjuf-o!I>rKc3e%Nw-cL| z5u0Y0(J!^$=jkOx^KjkLXlnLCv-If(+?7K$K4zwTU^J{3Zjfe;jb;ihz=qdo-wc?F z{xY~r{k|pq1%9Tj9GoaRKq>loWdUx?4CUdzy}r~04ah+hZS5aw(PR^TH&SAm{1h{I zse#DrLm3AZqv>>&8$gr~q4>6$a?k+&v!OCO&6KB%lw(6>o-$L`Acf?kkYSLI&o*OB zH|``MmvlGS^&R34Zk){)KhuG8gTI>$(RgCyPbd;I4FuHhARPD$5US?naU+sW%zo=g zFcyv!=^H6Bk0R+5gPS175p#_gtGh=)_k)ol-xw*fZKTNGjuiRONRf>rMJ^vH@|uw% zXO0wk{z#EIBSof;6#36HM|bOqz6_-e$Xpd_!1#KYQGy(nt2{` zNQWjv8L5v@V?PlpmeBu>^S_o54XtVcRFlzstsR=@cYfam=#9Nxy?mzcGPJF32`Ps#z4j6aq)tx;uqkNc5NZu?kqz z9Q;nYVUq5YwwE|St*9?&)t~~bAckhnp2 zE&K?*LO(B~cVZqjgsnV1VLK>BP^2#@@xIrXVvBm(H|p zrTbs-TV<*E$v@WJ2M#vxtxj4O8GZUGh;UV&$$_{x0}fpHK{0aR5_U2ioclo=@gNY$ zETqGIxETn$4dKs%Py^ATec08;W_Fv}6VwLn;Y@UuLyr2s>H~`O(+D`&a)ipW+Z^!E zc4+lBxb$wP7_A-4w;yc8XUu`^Z76^3fV?aGS)(_KpCn*woa$YT-uv;X-qz@a9jZ-T z)aXV2HnqIb`vZDk(dfltnKt#pM(>a4?QZlwlY)0vqxa8vv;AS;cEGa4F4+z^me_^c zo9UIiy_H^*w>Qyi{PsqAjoW??y)w3I^s;Z?fLCO`bDIP2|A)0Rfsd-X9)Bj2ApsIz zSRx=I5rU#|L4!4MKypf5G3W_@|nrf{rg&EK)nlMSp!~;}TIx0+soH&wVpVK2Qraazm~r>|RPaQl@Q9Fh=t#8S&j(~9NERGRCSWkZcD}RI!N>P)(Bw!x zNr4mkhEmk=LS>fAM+J89FVElL$Y?6rS<*(x9!GqYOd}y0fmsKCS8xpOoQmaR@Zz=ykRcIhHWeIi9mVtU7;UNb}Tu=vh6{quq;?*OEBL zpF~%+CzxHIUB`*@ChhriNXGx`S%RA|x)Dcw=cA`)fABbJ2|eklI#d`O#NWZ8-$^Bd zn`b365>&ID1FiMh;?LAUFF3N8HGQxIDQl_EPJANCF_I;SNVY$6ymZc=%qn_DdQ6Nh z7x|J6^;%W$Rl(1ydMAda)ij1^a6cv(!dg|8O(dX`*_#+#vXevx(u@;KezH}1FsRvi z6{Dcjpy~(Gh2U&5g>vY_jOl}eWdP(fHC{E^`fTm_R%^!<{X<2sJpdXq_+(4`o5H`? zwIiWO?eXJDk^vBt?Zgb}y^sjc0A1vlba6H=iLpoiO$ea-<{}0O*XD*MK&kxQTu#!u z^dlqoma|-YqE#-_=x+DBW6Sn%m5hRpRf#+!Oi72;K1NWh?}{axo{4Hj0#2D9$fjJv zX8s9>erhfOvIL8O>@PdUT28^}wZ}xpSYr#o_JeWa?`2pgpryfr(~KHMUyC3|-Mip+ z_Rf%}v$LC_RFU!d^24BAp3?I|1Sa^DRSKuG!o%wID2@m0<=qgstZkN0BKMmsJi4Ne zhB+U@92z-^|>DsMNp=Nsc zkRx=A>GLq-k?~h=LJ#8lI)6*r5;vlVETw(;%PrQp3mIJ(C_L6M%S)|?shec^YTe7T zc?dPRNp@IEViA^|%O)E2u>Z%5veM9Jk-UQ&rA}2_q+mZU*3|>5xJatN1|o3>VQWOV z6UA3*Ur{DZLQRy-SOGv)Og_VUejaeX?{eve8oi5?W{H!etJ460z(b`BGa{~QMH?1z zdH7)x-aAHh-Fls0yG?YO!cu&N3Vy1@Ro-uIBZi`0e@vpN>c9+)Q4T}%q9STdamsq> zH2jP>a!&{my*{5C=Q|j?aw)em=A=6r{7%5^A*L?t=vt-7N^YXy=>(WdZkj$Lc(%ey zlm*t-*;1rOHigM5_#;k!t=1&eNhNKJ%%DNwCYchiYBRMew=AQr({ah7VQw3d%hbXusZTLD7>UhIfs9_f{!M6K2zq{`@> zXk3(5{sU?GC#hyqfE0c};unfBBJXXq$U&F&NAQq#iE z=L>DsMjF>Qc=&6WAL$ZU_G7)cEnvQ5?Aq!tu`rBtYulSkh~W=ky(##yU)F!Ist8#w z<82Y$T%Y_=?j{{HpcNC-&j^WwQeM(d5kDN!uGVt*OE~zsHwGUqH~0F*6wt{^N&LzP z6E>Ea+-|)FPey&ep+4-vFjd~-9xB%B>GU>#Ntf`(ku$|IBM;w)7fe0n+nhK^CCG$L zOBStdCgHLG#hVAr*Zh&Wu)7E&(OEhE$Sh(K%^jn7kMig&%OAa@P$WKobZ)-Ld)6$W zW5^%ihiGDi?{35WrIo9gO{{Naju3l7!RLSA8cl(p*{E(d_3vs6lx!}K8k8?low||u=3@znfD&gMtJf| zvPD?`8m`hJQdM|zn@Y3nv=w&R&HQ9oFG^Z?@(uEmh6RjS**=n^d1rmXE^_8IB3lV; zh8|BU(&Bv$3TP^A&T)MO<-Mj29y~mwR2pRE_nzDTHCpfv)lutfUD76t_cE#3rU}*~ z)Sc>ps<(uC2~Nq1Ymi%5Gv0)-aesku*hJFK2My#>#r*h420$J0x%RI;X9Nd=j$z1x zXG3%gjjJ1LZKlk6$YTb(4Y=IQSD(vz*y*j4gPcA_fk|?&BV_UDCv28q z^ES%1vYCpirJ{*cl=w$#k5XnhV&B+|q~aJnM)=$ey}s8cVJ%+`ooLOaRLTzb0lPGw zJoqI`Gc5Or1BrB3CNe6L^k9U`L*br@%i&dbz^ZpzA4n(}a}sKCc3DfWpH@^32ki(% zoZ%njIPMrn#9%gdpJnejzXhlPDkZB#;mVY++8ScHbmjocT-x*0PYp!zReYXLEoM)6 zdnSD6X_^lLfB?^PacD>?4{b;|B|a#pebqm&`vAc$7(cfKL}#~8AG~}p(T*}n*{ zTddEO{1SSbmC?-o)KH18@V3m#@IjVGS9I1(m7HtBfjwQ6P+M!?r%Qr;>+;F|%tE@N zub(QYyS9sGZ`7#LyPJuwoz!ZtV~KOLX^*yY4djtp&s{oHThpW$H^qM=3GP?4@GAB2 zsBglxr)@!RC1?(Wc5E|aeZQeW*#8sc(jna{6iD=(AJi|LsZwm-+> zGvu=rF5_Wsm-^bVG>@uJlR?DCOQA~8QaZA76mL;|2nFocV=6%UIze0Y{WHdBe_zrW zHI}Kg&i=F))>T60RO;sb)Ya*ntNYV_n@;;}f7;#aDp)D1^j&?a+R7kcv~w@H#co?$ zxlpCeChwB#(rLmKuJb@~CH2B|>enQ-6DCknOVg=^qymC<`3Q5B#W8HFgO`ts4_UGsROhZ@<1laMEjTMsa52-R}hjQh2vZFWwcG z2pW8PQwuNVX|15I8nK*@j~dLr!o6L^?eXj7yP&lCE~sXIU(&g@(nck1x&35OW{Z!LN=b-%h9PC2g&ZUmZ*4rBh#&R2#oKmYkPP zm5Ysz#IF;yHM@#;Bnr|Pwls$Xy?8fXNt=^IB@CScmMIh)7M*3|8d$b7F%m3GB}6X* z(RPIQo};^W24`?ya%pLJTaNCwmgI6)$h`x!tbZZFt(;GM^pwN7_b{4eHmA0zuK0Uu z%gGimu3I36;9qtn>Y0MdvN3E|_v8AJLcVjdWV>q42~7w@&X<6~T%@;zkUL^2&bx6grh>ba^d5V zxB_B+l4?X47U+22j&)gj$I{e})ttEGzsc4jdscI1Fthvh|D4_2G+@u_KlYot6cS$3 z{x=|AfAJ2n4}7~j#y~QH0Lbsa=)<6&lc53A;)5@c{nWDj(6!X_M1-B#V7~^2VTjdy>&6|%r zi=B~P46$8>=JC4Upn_Ew|bxF@4zI$}2lBJVsdlxlv zV4Hg5v%aQyYvG{B(4=^t(89<{cP7C-!m&&v+?YxK9HIC4#5KyrF{MYb-O|#rAoCA` zB!p{6e}Ni*>MsyTY_lc^r6Y~+<0kc9BC%Vc#p)0uHonFB+Mm-I%emZ6DbLr}wv*l3 z2N4j%;9}8`88FAtQ}L@qm`oOIY8OuMA34wwF;c>8Y(uq?EcgfM0-3Lg`Ky#~7!l>_ z1P{0DDc>VevXHn?UZ$u@c_f_!Lw-)778zY{j%eJRbl=1hY=;*8rH2H?ZdJt-rEXX1g+NftFS>#9~ zIk|TmEh*X06Q1$Tqo4qu>KCFr1@eW~U;ZpW_7+VcJgwu70+XirW@5#2nF2+isIdTBzp6@dED>slu0E=R;}a zvs7oNi1vBD6+4vUK~}Uv^caL0;i(JdEAxEa)x(>8BQg@Btshc5niJU+PT_Ce)%nf7 z;j}Ox*TQo2FG%Jd`AiFi_N7>LzKP~chW(RL(pjf;+gC`osh}lmiiwc5#^5Y#K1yBS zP@^*4KhT3ieoE~C$!g(aMcr;MJM> zaHXd#L0N2vXwhWLrF?P&6nmOha~cmG9LhD(#5gM>CnHNvBn?~;!ZFoa@&}u6lvdx8 zE8N*on%cP%;3Q~Eteu#{>NVRMtNI$>9kDyv5?LX|Iz zQYtZxT&tXVfK2UNrp)>|)E4&;d#%T3kR&Q;SNZS2US+L81$@JRqc-Sn8s@!4_efr5$78+tJ9~BvBfM5Xo)NlLTVvmJwGO!lK_#~A0x`0U5DyiCMD!#4KJK#(7eJ+quRu}8M^Xzql)BY{EKHS8hrN+#N z6JPur1qKCXi`d2&sEB`q6J{DBc;5!(I4*Y#0KVfHw2H{^vTC4(&iM2*Cw%b>Z?AP3A z?vQ`evCy@BozgDDai_B@)vvLY+V^6WTF_Cc?QT#c8mAoT)IU|Ha_CgBFx8FvTTS} zb3c#5G;@hY=%aWYZ{fEls2O0Cau2KwbWdPXhkr+++`X$@oB3)<+uUf@f&d^LMim?g zO%>ngFK#z>9Pqn$X|+BoLPDs35AzqJ*6!@&l5Xxy^#pK<880)jF%}99oC-fv!N<@$ zR9=x7QmK~>4h|+Ulfi~K!m}fuNY6#{VS}QR^u>z}&+>!3nG z2BXGQ+{6qrrmobO5(R1sbuH94hjct&M)STJ%`h2_RwE2%dPE<_PnH$Lq}crr=BvJc z37VL%WxtU<^AmCV4Jm^B3e4A{KCs(hVYUK1)b*!ly?Kyiv{ zCUs!xuWsM4%g=#k=Su{CvHK0Su1VE%$A4W6 zKH1HszV!6CBU(tGiK;Ln<#K?j1MeeY1Pc}CD;9~hE-Hr?lyg${cYG7^Na8?~iXs{D zi6at?l|4vf_%1~*B4ghAmjonIUk-11(h=Sw_o#UPDqY=Z5WRqCE^>5gKIew(o|SR% z$|KHT(Bg8qalVEhmN@W@{3oUp5%MI4){V#@fLjOyJHLCyR7Aw2&B~m3r?#2n#ZYm z6Z)rm8+(R^Z_OuojkRDd*&+|w<@U&BcU!Er7f8{5rq`CE%)A8=q)b!-r&xcTE;+=- zBRJm7^N%7TQ%SOqeoH9cB^x3HG*&zG3$S%HYf1zW-l3<=|82|vQ1fI8#_crq?s7d-g z&iaG94-S~sxjSWr^m=N#*W39_uWuO8>$)Q8^)S_IfuHL2AIV2II&mOQxN!=y?pFn+ z*Y9F?px3pkp)JzuE!J^%ub&csV>pw+eLhlFJ(q5$=u`^Uo#_0`ya%Zz4eNcFep7BN zhr#*>9vA&Ckc#*UY~CFdb?^W1?x)qA+jIb zXT$pZ4blzep-pHJC0vQ@By@%0Sw_ec9mKCEr<)}2BxlmpAXMV$-? z$BVVP`2~j=H9tn*F01|*=(y($<&p=kPhDPMJ%~W1M^{WlWIZ38jBjL*@teb*NoET$ zV@AxHW=v`nFX?48)IpUv#H$;+5sZ|SkH;Bb8l*9zlGS}j9U_49^&+f1Mr~#f2umF0 z;dFFaD3d)r~!LNvBa*Vy6Q&OZ?ed1_%%T*Pts3ZRk&iFJwtvyW&$ej+#`H;SG zqpf5PL~nCUb3B)1`4aQFQ-21Sp=Ny1Sj#W}y6%j42RFH5sJJ0k-mpkNAL%n{JsDN-+8E_d&hF*t%!SUJ;BUJ4paG8Id zY9&LB+x^k##6{Rf|@3Ja&XCq4evSrRnXJg%<sfQc>b?dfDe1dP8 zbz~W?QdoAn5xp=;(5u-5$^r(%x%VZ}<#%rnj;Jat3%RNq%MzLO!w)sq4No`^=}DR% zOLRlM9zJ86`KI;kH(0>VPiE`D*{aU*sB z#-b4vIp1)$b5Y0$z0P6W*f@sOhEsDn@&m3Fz1Q7-hOu_&?nf5S7_$2jDdyZkNzBHF zm&6~m!MLgMfdM6l+wQXl5eG+>>HPQiSvNEj!jSg0wg;!!-C#OuvG;Em4bpk`xqz9Zmn z_iHm7@qlWzE@h*{xk)5fL_W`HUJgj3Mb++&l{D0Ts6EtRz|a?SYsn$*@$tS)*A3@( z>)|6O2zjwiuR3AvV`Ce#Q=FliLO?hsH}tt zE)=h=8wQB%FQ_vpNBBbvNw2__Y*7ao>8oeU$9&YQeZ48%FxdDxvdi+*t=riF(o*Mf zmFa({NE6JBdc~iNkQdViX^Q;27aA5DP|~0mNUj8uldREKIG4p+rx~^wlJTkN}@y z&d#aAed98(Ih)(XEXS?cdUr?jEXQd&;b(6jw{}8p;v&7Uo@MmxjTqca|@wa9bYOUo5fa3R3I{gQw9)NZky5pItTyH>+VoJ z#QmmuMJC?J`#CR^10~@;jRU&k2~`O&ib^=~cwe5ADa4 z;NIak+q9oGgu5KxXcoKb&T?ndg_6x$v|ACEnqlxAUowUrc(;$A7UdwupLB4_g?_@+ zi0V}FZ>UbkSPoWsS}pn;3Ytyv5?CS;DBckrk+b>63PnF&rm^{23(KHR;8haf+jB5>RY`{23jNAFI_6`YJ)KBsP zvneR+N#QuQ1$M?=(lDOsT0)_yJXBm{(*9F$k ze6pauMe}5hRg|Cr+|639V9Sbmoy}??GqIX?x8dAk?Nw@Y0=qji;y^zCHZ*`9|#RL4N0`H{@8uOZ79$ z4z*5qXg{=hpY2vF*`z+q+uN=6>g7-nZ}IKcUk|VEA*s&_zej!VQQxU=lzqaO+?|uzN6?TI}SnRMYcqy zA&k)REbElt%?4r>!wV&x-24HhRc9#V-0%@Rn@m%o=~{={rDV?rVIGi>v+e7FUG5ew z(1LKMJRKvhd&y*dYm{t9M#Ocuyls`JtYza@@*FzD&Nky_UIdUc(mAh_oR8Q!7f4Q* zbSz|;2_t&;hUIF0ALhp&y^siN4bMER!NRO67OZehk;;sN_sPw4{8|%k3$^aO`K|5pbi47-;W)k2W8hj{LnR9w;|%aY(xmC z(Y(=g#JH(pf#HmaZqL>&SvRubSQ{5GbTk}!KSecUn7Ko(N+yd=hV6H(ps}gRg7G4Y z%K78)!7Xy#qVXX}x-=~H;}Wjym$SxY55J_xUaap(us^DFWk{&v1Yt6O=g)SW4g z<(b-Q=?qn!jQ=Ks2)8+mvb^J{tM9C+(xW#j{lk&H+1 zAlqtti+b&E<6(b~Ynhc#ZnjoD)gwb&W);dC!Mcw#A~^YyG2$ePRKhp;NxXelgFP?6 z1tT{8+S5|%)K?)HF>9~{DK#Md#|}XfuU<6jPR1BsR0tp!v4s#JECtb&Zb|KheTsPG zvtCXW&!nY$Frp6<-B$GM^MRxgtU~tfgQ7TccW_LttVEP+IJj)F_Ls>RnA4`5#LK2; zT}pZBrANdd!I`OIZ&%jsRjx;=L3lq|ib_Mx!Iga2yc~7}4zpY`Mm87JCpp%Y0_}gd z9;-jNN1zuq>XHS&d<^wK$$o;s1dG0@)hJnrz2pX^>Zq4&i^g#1KWjrqAq`#mD?Ddu{a(C}q~o^f^Ct1DC}e98 z-wIhJB6l4s-DJYG4t+Le(0BhLGlr(!;IK}wV;Mf|oQQgv zRJnSR!Qye(%GvuV;oi*9AZqs{P7n7wLnrf?lb9q4M$4=zikg_N2W#meg@*dgtU)cF zF_KK+!Ekdvd}T zyTi`%AzA7n>Nbt&)1gCoqBAa+PMEoFF*yve!dEcBaFLrokQ*F)Sg%W7LFuT7oLWq# z>lCO&AN-Gq&0uoq9X63u%dm+%&8^~UP!ZIk<)?LH4;d1OUYr>%ca)orB|H7mA7pm# zFE=+65p~%hE)KsektNrM!^c+Ykr4nUCqP&W_m(BQU@}j)Vn~rHkx*l%JCF{j$#+g`C43NsQ4 zRPnAwDJ~0wLq?xSxvhcX1~iE36cTO|7`{B2Xav&-k=~W(fhUokk|y?}v9OOK{i<(| zJ8ECHO2!m~Z$7*)a`@?gXm^vedq-cpPY8_raGRUp)RR@P^B+2 zGV!ntG_# zZSt~Pol1tpaBDE1q0ou<@h0R{7#!Z=teT={s*inJJ3CA-YTqKatN$|ikd5qbAX*@`IXhQ-wZjx{-2V67TQ8Mz1xH|Oz*~Dekaqp$aMif zf#@^^@lwD04cj$HZLd%n!yGLe%?Sg#Uof!y!&LWklAs^F$$G=Q4AkXp6R}#6M++si zk4J<>LtKh5vd2!0HN{VPhtFvKch+hueBC&&7f5`v^dt&>Rw1^f3R=xk2PGu0Emo}eR9-<0t6 zg+QXb6O{=r020Sk{IZp$9gDJxZHjfde2hBmB8Gn7yVkw*vT8+shUqKRe0T_i2iltz zkCA4EO+N^r=6x>n9-M8ui*Mt$k9@|q6ihF{;&GPnde4Z_uNB(|yr1>sYha#wf4#AR=uiK>%Gg=0)9jIdbQ_2<%sW)T}bvInLoc$0tz-KpbskBF zO=^l}F=^>LC&Xz%B8B?F$f;u42)}>GS}4saBpqx`4G3~6ca*N`Z;qPKBcJBv@6X5z z9%rY68pd&5)y62{Xj(i71WQ!mmG{$~Q=M}0H8k%$+;J};A z+^kfSUDf_k;$SQ=nHv5E^B+M)spmw#Uzk9!kggzC7^AAl6Iskc0XdAE@WOl%wm{(0 z!x#~@1iZj=jI6D6aGr%mq`ZN?1sCbv4eOcp&=qL$y}^0BZjjepKTL-)19tDu<>w3T zQ=k=G?*RW>d2isoo%c@GK}QPrSr00%FI;}PS{K&W1q()6(_ZXjD_&|(vlRsS{W5)o z`piMDXCB8VzJ*-3Mfm!X@n;An+O-8bwt}x>qVHd*%xPMd;TxOXSI9CyVp!mi(k(>t zw%$RFk-Tm8&vyH#oga~-rC)`R#d$iWW)Ry$D5H`Oy(bWGOkeHr)^R2RR*LJgd$WSx zaPJ^AcD*^lbHlwugJ*|(wO~$EPq4o%uY_H+1)*>}i|bM2cx+YEN4Y;>si^d>FGQU57|64iT*nLyCUgFhL2OcL+$f1 z8|VV_aE>J5gH>>4CG)Xa&Bs2%Ct$+Eb6p&JQOAackxQ#Rog(oP{^-x<%Z_?*=<*r5 z_Q-2X&+(f+u(YPAO7aU)_lK{~Awy2+66(H&~$#yQZlmuG1goNZy z8RHf;#!aH1tfDc0#FrygJZ;UBDgq44$WQ!1b|^8AfP-klbg{c$(_}X^RDlnTq6Eo_=KhNRH{0r_F=K@Y2ZtwJmx8g$rtraGOLn2O5MK`X67ILKiL^E;EemVI;zU@F9ar$YJ;wJGC7C0r5ti$@!NvKkP@OZJB{v7 z@f2P%%db7rVIMB2-2;0r=L_DRsAh4B?5@j>Q_t)uc|T=BFdiuVBz!E875-{RBG3A8 zn=JEO`p~!gLaW@Hm#Qctq~WXlGs1(U^%p|!(^O1P1$kOXlp)(a{TS=l)MrO_I)4j@ z+7y47`O+?fw4cki_^EcMI5Dn31ZfBoyK{a;Ws9%kl=IjD|7{Pe0?!DSbut2HgDx=x9o!GsIGFZJc!Ki5XXwh`~}p{IFZ7NF{zce zmz#qE=12bIrdg%ar(k_8Zs*x>&aWs(>4fC+E7VqENDng~_+T!;!q?6rp^gh8S!ep4 zqR)lm`Ka}?(9?XE>lGqnSfF#Z%C!ow(YC+%O~As~5~;DhDRRmv+*GWIWWw-U9$m&E zhl8OHBoNjx(uDFnQ1q}gDU7bVsZ|Kc)pnqx+xsH~-_@p618H37)1?fE`Gd-SD_!}d zBw22D15N1-r@*2 zak$!Uef%#4XbE^WmJ1t+%gpo&&?=Q8NvrK(1m(qtE{Nuq@CCoBa&x2QPkn4x5WO(R z8@&ZJ%`!aKw`sLdqYR_$yb!^Hc1MjY>n@sL|BDQ=_Jh0zu07xcanbccBSN(7$n5;Y z&4J>t8Ee`0<=>rxO#ye;^5e-nH+t&VFtiX1+bTtWFE~7rsd-G*w8i=zzZ=v#YqNeX zOQR*8Nfq?Aiw0Hw(OipPJglwds*p4}HsH7;C$U9^oEk6_nS^$dkUuK2tXd_M`mjZK zX5ll^G1#g<5QdJ;Zr?S~YFmnyQtbs(Q~29dATubI!IsSim%>wk4khTc`Mrs`wct$*SJdmgiDY zjQa(fo^&iB%twn+{IVj5_?zf!J@jHfhbEGNNkkg4E}pIew#p$7;n=6DaoLAXl8Pqa zj*Oe(1p`}LMvDkqav*dtODtL7!(u3``CUaaMv?N@pCZ%vKvAo_S8o(xq@$$y&F|*; zi%&OVUpR4(xa#!K$a-(l)rjd6r=K3m_LSaUbWLzH%}JN9QW4Hywz6oEX|1a~+dw6ccSf7qKqy&zLE7CBj+R^I>Ra^ObYLerE9DJ+{`OZ_469(XW;)&*Elu3Ix$|ll_A|4 zBi%Zwzgw*vWy;0Q!hge&fd~+l3F90ZmrjkkKD!q1l{rMIG1wYPS=D>cJymR_;;E33 z27+FWENIq6n2iq;6cj%IGcQ{+x+d!u&LxK@mxbtC_~4Rve@sMVY=okbbLf%L~XVkM@#Q%RC8bFlq+X}->ZJ6kg5ZeH>^ zixvK1UeTp^0xT{}OvGn(rUPq6BG0{TDHp^ydvg~T;$h-$U53%8F|$4skDph~Sy@6} z7OTT$UUQo;`f67)qP2^Ggb-8_7n~ku@46xnni<-rl=3?GsQ`;0k>V>C^(|(M&?#!* zp-{!bJgi9i^2@_Y)e2=gyx`Bq;LbxTgTGJuus_og99k6^GbX=2;QC&oV1Fj}zR7!G z{avo_k&#P(+v@Iej^R=1bb(3uBS)wuF{@;!0uv5@M00(Ja9D+MYvnODaFx~CRQjw(BGUjzt7_nuqS>d6yn)?J2 zTot4|VnAL_;>&7eK)3(j=!f6gDx}TtY*5rqFn9IgH4!b14PNI!@d>DC~T4hcXe5xihWz|I5O248=LYtgz~_F_a!3~ExVFM z!1*ATb&pie)ebUZ2I`xNcotEmowUIwNaMWXd*mL1*FYH$scUX=|C^X@{W4Un09weVWv&@&jty}Eip299Qy8anNlpM{gL2&Tw2tyw2xEy8nX4HsYnycnV4FSvFQmVHTsddY7l>_RO)NW0A7x9-AA41JmF|lbTa511_~! z9fc>6Ra_y<6_+FEqUbznVx53oUF8yAX44(kSF&KIDU<3d`40M_g01$;>Adux7pm?( z8&q9h7_ENM%?uM;ELVT#k03jWvBo8?aPKLhGfH+QimQ8n{xWz~Sl#>R8yOkP##Q(J zU4Gx?oCOOd_?pdLgq-2G@gti2ZJHAOPqcar(ie7g2$ing-&C?E)!w-N_J*vJ=Cns* zOUm@{fm0I2AcSh`6dvrxcu=j4;XyU0@gNH{I7oYDUpDxP2^RZ>uLiFvFO~<**TnyX zk|9wn?vM=6mGtXXd|A-3h_d1+{%M|ML5vJ4j-|az{tl?(*@0zuDkcN)AC-mNlr&pz zN}4uXZtUarYoT}LGO<#l_*45*F_MiPT+!JC@1yQEDcLC>3K1SHp>J|KXK*eaFxJ`6 z2}<+`-+sEPgA%F^3gh57MMB2gywVdCBp1ANx1WI4PQP5JxZQdhVr{?l*-2fDA%!KV z9Qxua4{#m_XW~!^75fHS%a+xrV*qjCJ}ivCBDrE4M+E3shAq{sNi>+&WF>U*=N)2= zL)Kyxpf2|7CthdW$o}qEsY(ZR44ROo!wr-s(cDso2A|w=nv9$d%D#34bDNgc2J~yd zUQp;Yd-2p9)O`r$$6It3{l{GMu0#1tGud+rzWa6AO7Q>3FWTH`lT+L&Ai2s&9<>u4-+aH%R zh-jnWIdeqN*s@B-EvORuda_`}58-BeE(^_!E_jkmUbfa~fd!9kUHK0+cPHCP?~x=b zEuWn_QpxrtBnxh{>jRE^7~tgnoU}oK%jVYNUsa=emOus4lztJcu|@JBG%Z=@8{th^ ze)k9H^PvI&;~N)>zom@l0zdBdBEG+0D|s7&XQ3IN$>(Q0439f0Fb@>(s3&kzJFxPX zw9j32uL5b}H0l{k#>844@_7KVN;RK~G`!tka$xSNXH{k)m5M%nRq$n7uZu_mlW-hc z&N{TG>=mwD*?yu0gZZ&CWR1|nW56P#CC0Wc)}KOIz`p=OYcZBLs04`L!1{Wi?dy)W z>$7;KzBoVDlW-qjQJ??c)%T**H^HuNqg`K2>Rb5L_4P6lU)jH+KT@BpJI0&OGyVJI z$JCdfst?})4*b{yeNtvL_3UNFQu=TK+k1bY8heX%?ckYe8l{>xL;xxPaJY%swS0R( zEyqwx8eR(gzn6N1(WE;+w(F^vdaA!%&%^=sJYS<|sL+iROZzIFT1W-#cF}@ssX}e3 z)H;hq>ny5cB5|9hXF2%A(T@ZdWZXeL_UfmzY9CYbPQP89eUplRBo)(w>kb~o-Wx3# zB~6T=*c(xv7O$o9woFV4!WWh}7hRZrwAti($t0_s%$3R&1 z$8n>9fNDU1b*?-`3*6EU2CfWihBQF!Covp>iVT9`z8bb0@JQDU*_5nc59btK4|Gga;&#=JZ9&3T;&*R zJ!R+6R`1b#Z{qOSKS1^wdtFiXkB`vzYyo@J^;4c{;$D8NrFQ914x~+1Ra3HHlp2MQ z{or0o)=J4CcFFJCC6`FavQ$Y(bjl_+V12i*qzUw}>_Y|58thon8NNkg_w1*vG>F|Z znm6M*k$X7Jyj^Z2?cNc7YvQNIU&Qk>ClxYHT2(Y&M2!U=Du_}&pTNDpCsGXQFT zJow376oLEg^HWXn>V+!wQ?FcpCD6FoFy9oVBz@+tugO$~!dmQL`G)(A(0lyB7tsAC zPR!vZG~>7RWC$0p^5;lc#!suHp9mf}W^*fd=(V)pc|R#qYb#AZg{-+ZC&r)|RbWX+xGW5~q|TE5SyV0ccstVw)c|Bz_>R5Vi{?mBm1((3N&uT6pvn z8C+;osjpK)s{2|~0o{PtXl;$pn$%piqLb)@hFRX)7rVB+OIGDa`6+!QUBkIPeeMc< zq@tDR;B9Gk|7G?ZO+lJXjdF5E75a!T>(QA)(V~0i58&b#%7LW{BXYIJh=dF=9s}LK z%@XKtGH}B%7G1qiE`pBC&+v&n(#t_=pt_3#jF#N9>u!oJaNN$N+Fmns3!$aCbIQ#2 znu~M0e@!Q3bH9IciaU*{Y-6=(0k=V1 zS_ZxF0-Gtk>t^dRHZ?gIz~l~V$$0&BvWJ`VS&{oy_J_#sjnF&0vx0JVXd=38Euu%# z!yk}II`07tP`B$aQb=$=9ys!BzJwWGROnL}1?F`bqcv7+CyP*vX*Dw_{EYP8Te~-S zvFTeZiKme0?$Lf!`0r~L-a{%ieK*)R727X{Kx(M90C4;*{s`ygY{PO3z*^OP*{uM~ zy?OcBqyelLX3l=l3e2Y7l!BQ^3AKB)nqN{)i&MdDq;=w71z>7t?E_eo08EK6Oa2WU z#H1;`g-jG$qvwg(mhC+Bumm&rth zT-N1}qxylj*dN4K<--1SopfPo%5#j8t=5J-=`vVsg2g6S9KAk2trfbuNr2b~!Nh$7 z>i?tF`xv78!~L>uYA;&MUgT$yiQ=e7mRf~n&ZEVeq6UFD2@9yz6Ht7jDEDzEB>|@M zW%4{p0&ft`8g*z^rfv$)58LU5)^gZ!mgo)YusM|v3^QS$sgWg5I6?GEu2&J4Q|q1@ zQw#s(If9WrS<8<#fu6dBpc-}$qCAK55(G)CjG)t&^AV}B<%=ADSojzA+jDWtON;OO z0xWd7z3d};NjrGg+Xu?LrDt$z*WSm*dx;h)Z!Fo2gjPQ%Qw-Y@6VQ(ncR7ngU?ry? z>t;(W?PAIfhGg-53y!0r8g>kgRT&`_9LwfZ#}ME#tY&mTmtzpmikLOTNN%mvheBDM zYb8KS<)l_N_{_B#ukk#zwK6i)a2}8pvDj&~*FrDw1Mx@BGj<#l4eL0+`33&?#fdm+ z)&YY0+My|x;)uOvfpEPGWiw7A$hB;XMEw3=25IhQi~fU8O+hmF3mINmW`j%GIPB@{ zk$?t|zwkm=k~L{ocM3qs;#B(o>aN3IWgQRrDz?AA*Qj2quN@>AxEIF*SuU&SlKmoI zv?o*t5N@#)5>>#nq>~y7{luRLZ6O4gkQa4)`e-s22vFT4m4j4t?^f8{V_-AH8}Mes z40onF_6@nq@+!kL?T&MIPq-{2G~9arQCW1R)L`u`lxj?SEPXj17?9(|6I2eV)T$Yf zev5hzr-wa98k=wch z!2A0Yo}aS;c^d{z*Zo*vjj}miawV-M?Cl5{Q=YZ=Pco)q*4y$Mz7yDDo%uSjk-vfl?{sD54wLy4fFFjTVO9xo`0 z{>( zzL*aoci?S7OK1RRSBwb%du7N zvu`g*WbwjvNUeH(#`Ln_zC#UqU4~wD?wQ;Pl+?2lTo$TA?98gVJ(IKCpHquJda=WA zZnh3$GCy*Vj5GaDdCOml`*-yR2AhS~9IkFl{lv#%zC6lRZp37M( z2`gjT?}uf1pvXgZxhF63SW&5zOl}TDepe(m^#B!&;a9k#_koPp{AO0khkE#5j?jtb z<;n}fi^VQ0aynjQ?1G+WUJ%mID88yq@xOvt(y6bdvqnx9XEVv)m-T04E!hdz(*0TO z&d8-%oQv8NIX%9VqWT6f7n<=!!2I0j-W@#EZ@!>S@MO$1z4vFKsE)l~=yUG&4{bJL z|131RKQS8w4$dC(hW4cTBf`5ka=PjkTuzpC677lhIQk4=YOmNhgqTu-(c z$<3T%%Od}Zcw)59(a>VT`Lz2N#mvADv-GmS{aH($y4;dw--I2ZL4Y3s_+o_hNv`#U zG!UJ0NH@Pj5$(Ac9~bg5I8-Ezh25&Rd;@W$Gn8Iy)J;x9Ar7pavM7eaLHG^+SxnNNmV+ zr<(Vw4zlj1DI2~|h=nlUyDXE-5<3G!8}wc8V6%2E83Q9zNQGb6o%0UuNVtgdB6cDX zO1eMwnvL8V;di$z8*M0RFBsx?#>!!hwI`Ya&W3V#%kpBH6mT(v+7q#R{D|hUw+j73 zx3K>%BdkrEQQR1CHj#DNJFIlAhG+pu9UP*2ox{-ZSg8Yj;ifFXH#8s7E3>33>Yo+O znnVv^zhZj}rJK6Df%CnNfHrFUoTb2-{r_C3=H0h7MS4aRO zCOhD?^za7`Epk0%Q>Q29X7@2mM-%Z%n^42J07EPpi-1V(VM{)X9K$>eOKvXv&iz@T zLGl)Y8{ZT#ZR`t}_PQ4k2vsmh7m^Twa0neZ3IRdKIRBCX&J5AVil&HWu=;}z)Qt$e z&rO4Zi^i_D0X9L4iCh}0JG z6!}LXe>osI6Bakq8?7m;iOOfKR@g+a+KU75yT0hPnO?SlQb+-pJL=(gCg?HC zWq!IrAf*?7YHl-j{WH1UY}KCg*4B8h)W7{KZMqR^3Gn-2PW9sINc2ME<&)z1qw!pZT)S{dw@~K6m?)3s`9^)~JzmH!el1 z5~!^q6jMnX2NnjI^P)9KKKDyOmpQ*G z@f1!8F!;IQ>!LLo%26=%8GV9=IlS7OE~#~Y`1cA{x*DA~@|>7uy^&B=ORd06hTKv+ zk010H8S%*^0UyUv;Nvw9sw#ZN2f4rYa0vZV?|$*Wgiw@b1VXg}o1;LeLLfBvzk<+< zl8eAn{}x0~{?8yXLA#^EDe@12Q``Xx>eb0ixIwL^h?IYakFxrXf{!1{=u`NpJqN0T zjspPbHTMZhLB`E??j=!pGTRK?XXA-i&}ODWo6M57Sp0bCaI7? zKN{R`2rd{=wG0Fg_2O>xkW7EMAxzj zNUS}P3Vh}VUUw%e(-K&4@0O3NZthySWD{ET)laWn$}ir;J*d>|S&3#1OARKX7OT}Y zl&vkAcO%UhvG<88RkLm}g^cKr7d|ULYMMoX>EyiHsFl{#W2M9Q$+Y49)|AQeO&tb zYe6gc89_iF!Br#oDYp_@@vJ=z@fwx`D;w#~9gCcHEDn-P;qyb7!mU5=o5Jtj*EfZt zA3Y$#-a&?ah-TEVKbHp?_OAbB*zqio2F^dV4t(Z61JTE21;Cia>6eVcr_FpjHARLt zYlrrehMEQurVJl<)dCONV>Y@#C&8;4jUuzmU=%%c3pzCms@}zg$|;YHp&#I@n^j zbl(vznKdefX4uNWpU7S6Jyop;LtgV0MCZTTl^suIstJWyFKD%&FjM`6=-(oNv}~O{ zx*rMF<$wgO_xB;ejX&)}0x#5lnimC+=SfEmYxNHT$l~`{%UL@Qu{!vw)t(Qj7oHCT zRZc~@S0vubL5lxf!_VP-u<4UQKu4>z@|~20W``K5@Bv!CuGX&ev4a7g@`3v z&7Ln)D4|aH+`&B^NuMgR6@6-J|1as&Jq1V5r&ToHN1t-xUW9nZ+n@;wv<*U^zh z9{PzB7w#NEnMmm)OWQ)P3t5^RUQxAl>2T9m<&7x5E>xu_n-jlIwE`VlM6DRSs^SZH zKqQ0+{YpA?bxj`y+UJE6W$z1~n<78uH4>JnkM=x|)jdsnW)1@FF1oi5?e^UxqYYIF z;W+_%p$N~1LK$R8@5)0z;rWk4ice7_hvR7TYkcPW{gh>MAgU<5qBn>*i=i}<$*;|7 z@25B~Gi*h1{?SKqM$&_s=Cnv3!Qph_r;NX!;Cvr~qw>f1kp3SCPV>E93>l!7+FQ7wIm;MfF45gb(a{RBr<`z3AkN8O;i=DFc8f$SYY?5_2(m?v|4;$Q;RPz z-D=WTmEcNyHoV7$IZ0uxH*#}TN>^51V;y7>vULin$Mjxhg%d29o*`Q8J%A+H+?;); zfuy1+Eyn_c%kJ+^joNV!>ye!#M4^vSxgt$wk2##oCKcO~a37_Ek4tw_eN-HgHIHs4 z@?od4lG_qFS$IKQ>!@RU6rBPG!6J*ZYWLqVL~M9O49RrjsudLl08#uQ$JlkXnK3ae zi9f7pIfLBbE#9TC%b^>J>(#qEV|$(Tn;YKB- z2HU*MJ+8_enQijBoBi6?VM|eDzNys;@#FG`1P3nD%8)mhRB_uq_YPYTBCZ>WYx=*h zPJKT%_5DHleqR6gyHno}tgy@D1)L%7yy^NYQ{P`reXrxYeII}daInyNyqM9OQKclp zPhtPh|188#vz7=0_&4#zoFsB6vR@>eT@>C#C>oHx89-ceA${Z4B{lNf^fu- zCB^Z1e{hp{jptfVN%@Hat>@Lsw;!chZ8s63!5BVQilB?N@Q;p=E)?3kDub2jL;s| zEdCcV#s^2r7&E_OjGRMA;}ZS=aspwqRrg0(;KBH)8wYn-_6PpshnvQ>Hw1r}EelX-~B- z7HcQQHO-D-JIj6z4TTJhWMGg~%z<01rm3xF!M0XPpB>E-sT8tKVU-SkLb zIYW(<<8oGIYNS^)Qf$PSAZ1L>KN_xeK+V$OZAtDLnh{5jT4 zYLwW(8(_S!7)q?+h?B)7ejNnQ#Q!whdkBm9fx7erCT0@B^K^G!79?ZoiCkR)`RF-A zh8h!hN)?9|@q5=P zQPP@ zwna%?WhZWwL=YX_tkFg&P=&c*pQufhE$=xy{d-fw-W#pe5mNBKYBqFlR5;E$m)}YBeII?7 zJ5%rt$9qW!)<|Sv#|eF{>ez8qctGcn?O|^f5P5ZypwqbyPz{sLucz;x@_Q3~8|8Ne zefx-S!3az!6L0Ury@glHg+@XyGzD^@S&$3GaL4x<9E~95LQS#G({a=~97uFFpwq3W zxe+MGpHSAp8a{fpae8Mlgg`0V#5wmTI=4CZUqc4eXD7*k()mVyILK`u4AX`>Z*1$E zfIQOVmStReQ3!vcBY$mq#+30$Dv|-EC}E~4&n?S%JJRm&Wf>=84wSZie6D_qZC(hd z*e8h4QeKc!pb-1GYljeI#7tp%34$1l&NLlNi=7lvw;?s;jyw1Y0YwkN#dXW*+{zFk z-W#WAS2?4GPBf9kV$cuhUOB;EC;00Gf6Fu6M*+3or|7Zwj`jp@F~fhf$93bQ2<3g!RSl@M{9O?O6hq{S=zVnF z9kyZXk`OA#PBRs}Ux>?(v^woBfUQmmL2sP1<@hPmc?L;t!ui(0Y832zL_G~0`&|DZ za#@Y*u3;_ZQ~lJtARe^JdCcj1;}B#_O0npsB_pu^xetVE<42A@X7h6ORo_S1h%fUw zhP6<;Z+UT`tFzNrfdxmExy<)M1Z<);s;;EcuBO063`9g*gen5dm{`$7@j%Md$QEM< z+Tp8O3_2>krN8e~2-Hh6Fa4dF#wldRU5Cl|ips{ChB?jRg6LkE2vFhOR_A4GJ-i+6 zIDQ<5RIyfH!71rK;@lmd^XFruqI9;2R>zX4uol}mRsRYNcXjrG$bntL>umwpi_zm6 zC#2JT>s5(V!hFtK9SN`uG4w9#;iXywslJ0xhl!5T^r$4k0cw}U(dJ@b2eqrg>O<13 z`i$GUYYJj;Q*_WPgAs#4*0clYgRjAQD)t+Q`#I>G_Y>5)AHfj+=s#On~=P?1T(c0*^=UhofKz0U9{jxe&kLg=H0}h8}Fd% zgeYhqNK@fF!e2`$rVy-A)K%<$<0CMEjq#|$8q^J8l<(tIi1PD3>OPoeD^H9}#6n3K z4x=ca0rB4b>c%h0Tz%tc*v0A3n302%2_x4<0o#Y-$S%y>6O8jmi@6&18gS4hbYW5RQ22aqPD^Y@ExQ+929dOc8Hw8Cq&N@ z-o&2m6MG(nkBGBcr1=QZ&KeXnDK-oK4RlRMJLsB9;nd=ZU6OFFr3O(8v0rcvBiA87 zF9hku#q$Z65;v(35RkDzS1ix%rVHXpvs+@LGMdHShX(iF2XUWz5(J>l*z;h80JIoW z&zVaVV&jEBX}G(`G3JH=#Oe0fEL9bZ4Y9N&-uR8s6lKfv%wlW}p|hpTm@ZpIdxO$2 zQhWb#MZMbq!RFb2E7o&g|CSibupe&@IpdX6K z9`1^wpdv(i6ct=y2U&A00VUy0rj0a7G?+^!(F+sFj+sdE#H#*@st%{cwsU)=zK;Hi zEJt+oudI7K&-B1aHttO|A9X}w9fqb4=P{?@BqZN&(Ai6HUWs!y##gtrbDTma`BXbP z=?{#+>*~Qnd>o)xo>-+W(TscpBhEw! ztYaq+N?#Oh=MlU9En)z>69QenFoOO8u^D|x_1#Ztc!=(bVcA;fpoG_v7vbsWr z4_$+n;Ag=F%&aZKfG<3*-g{>mPE;CcuPs=Jz*tKD@V5^mi&%yZBFor7v@H%x(&RIe zamKcm;M=7GH#_dH)fL>*5~FX4!FKXL3kNq=B7s+8awB*gdd8ESftN~SZybw6xKxr5 zImQRZT-e%s-$crwLfBRAC3@*w>S{a}T18=Ax~&AHb*`#yUqjsGRFQ;f2{mveOruZb z^nD3Sj3Q$!3pGP0Aq_ny6fDS8;2uv@>*Yb4Fb`otL5lZQTX|%t+eal4s4(}k;B=Qul@@1jp;0Ff51&1`gBJV?l z5jh-lhuF=J&0}Jp{xGZH^K}?LM(QT|MAsBxugmWR&k_%cCV&TMVbGR8A_7f&s-8J= zMV#?O(L)_$!cCsIEe02s;2c@^JR09;C3&Ea(s=NEhkxS1Td4H^nFj{PLzD^{A8s^k z#ytW!zXB81cB5mW-m5YQ`kC0|fE^Cl0wFd!fGJ~{DUjNfnGH;_GFNEh198Rs2#Ji+ z-p~+a0c63eSyM`^nG<&n);##l|HK-t&mwsvn%;_h1V~j!X#9B~$jzS==!iUmiO8*S zkfD=#V{TRszdZMdC9OO9pVq8eUDsc?K+#X$74< z>w#tx^211`wvrAp!Hnm@EvUj(@GXAWdHfF3SiM?Uz>y$u)eN~v+(HW(-`H^MbKeMN z!f1%ME!2#>w-*za;Z)!e@W$aSc!Gb9ahJwNLWwn$6OD%@yNQX&iO7Wukf*iQ&V9EU z=2FE}qsPF^6h~)^oj`w)4%`b-FBOSCn|fWb)*LmKgL`OS?uqWz4YfpJHu{eVID`pZ zNF2%3t~Z()S(8t8#{RCHk8OtrCJue`p1;GT{VlDk=;eJ3*JUH8J|;bK8EA3c`6VnM zVi^43qaAEQO1_7>kOQ;4)!G1?Y=F?EPL{`@I6ODraGNB~@DzuIYn{3j#1n)2gtX&W zr4XID=e)o>2soJ3cI?gd!W+?{`>arS(^v#U@y28jW_XBcB!>!C;tty4B=J!?InZTkg9|CrqQ47{XeDJC3JZWLWk(Owc*t%S!NJxw6kvLQBgSt5EYfr95!Pf?+$=Mf`WwLrfU#D8HM_e!H& z;nd#30N491!7&!()VMl}^JwF{6c7mSVJ9m_$ha6=-Fl&=1wx*RGg&WYdJ&P2dWtp( zd@w}aKhocn&LDu8-TQy#irR*ZQ_<`4G!>trna?`}pOs zDr7ioA8LUy>4NZ~^U)G~i{KbqkKl5%2(|OhdIT@74&tYAXtVHQ0S`GR-I>^cA;BQW zMPGXl^(W*;&d=52lN$KO8g%#6RNzKBCk-(>-b1GF`B=Xwv#ou}SllLgFBy7ESOP0? zJ_kM>CZ+LVs5|3MhNJ8ETqw!YV6c>ORr88mzGsgt#OQ7%LDT}RY67wox2i^{U>BY^ zM7WWrUAxo%k6?9-@J?j#qxlG)6%)2}B=D)kjm4p;eOF8NN=Pt@{S0Wq4Z`HZIW?uBI8QTRIvr-h-<*Anb6yno;Z9AJy3tAyxZZPz1w7Q=_A3hJ!k zUl>00FZIIp-n4fGKG=T{M`iC7LEmSlpg%B31~?IRd*gWO#PI#A5MDSD*wIqz)6pZu zsyK*Q#``g8IYNtVi>)sX&@#8Z8~g z`39GZdO#mcEW#c%w|-AeA7^WapSMO|SR{4#3ENl3QG6aeVU0dlFp&nXC^R@IVGuT| zCWP-3;bM9FzY8BGYpCyNz!ka9=Ej-Cb@V;^tcnl+#TxzVA{kW4yvKxRHU#z0f*#P1 zkN%khBlYiLeX)TVhvVU7aI7X`hd14NlX@uV_BdjzN~ zP{)wU9W}>GA?k#VF%?XUxLCkFR+9U*sAypP{0%Ls4yN*nAG)t~i#ugdNlTiVfel(wyjuPST!VMs^PrABlyRlo5oeb8&ag#A~@s5t5Z9YY-7 z*$7db@>$}}SXW;%;?~FcLZ`KBs7}!}RNise=moJpYJTM{R2?RiPVd$dTYg(oV;a=p zzuK6`>$r^*p@Mn|V^p-vzMpt=b%c&3Nf)7`*=$1a3X68c`j{|rQAG+%npS$bdK*5Z zeI?$8533vveufVdI-3j|s&RaLDZVT#As=V|&ea=*r&>%f;A5BLI8JF-y}vlIY`Txh z-Ww5&Td9x3AtQ%ryBE4ulnj^lBN-gqlCd4P%GIbAeR+k#r#-FnW@3gA$$@(vSG;+J z1oJT)aVegzYRAQP)rMI|;>%-N&KdI1Z*1;RROnpx@UmpY&U;5oL#Cndu+Ei2#LG3C zh_~xDB0jFkM0{PbMB2H=5b<-3Afk5-CDPs%M#SIMhe&{{JCQ(FCn7yXI;T@)a9ra$Gyad)4m^z;onGr`#2wYxJrb5U(0d0d{IZv>l)t|ZWmtkBIwolDnoCA-i=Q(^dacexPqYwny>3V zs9tPGTJ2nU^zBFAey%k7*3-A%HH*I6(|3Epmy7=N?e7{#>H+i};2K8XBG!Sff%F|j z>Orm^^xc8JJGcz=O+SYXE^osu>_R~HY(jXDItk}-7F54sP&kBrS-tZ zJ5Z{LXM19!=0XDZJ=hj)4-F;7B&{%YGfaWGC{JjLafu0%-dOZbIObjqMfW-%c~Y58 zIbff2tKj1$oht*Oy5!}$i-@;~@FgEt5+Pq#Jdt*;@kIPw(M0sF;Y8ZIB8d3A1`r8w z^(GSN3Lz5Y>PV!6icFl2x_O;ZyGYS8ArHbbYpjrV=LAK~4KcNg9|yoc}}#rp%^pYWc; zdkL>@rcTl0orm`xyd`-5h4*>9Z{qzB?^e9~@P32$6yCFVui*8^L8D#p_Qm@q-gog< z;Qb8m4!nEueu?)8-V=Bm@iyVTfcFYsUu5k-yqzeE)2hog&V$6@s8LMsLK1<_C$NV_ zO$wv!#FLEb=ml8cFTF1eH^X8_ks~dHeoWYC(fAB<3D!W+ZVuapTNqk`f1sf}tynKf zz@=`uMpYLd-LxpShiy~YIRUic+^x=-^7qBSugI0g?=X$GxFPtNzJ(+Hs>r9r_G zs3S#1u#;*r6S~9PgtPWAPlRD9T9D6MTtl48#O3aoo)uLs`be123ZuiomW7HT3(aq} z1YaIO1*;y%QY0(5L6K@@^+-0gsJ75zF`YC=Ttl70XPmWNn?f?-Zc$YTjEH3FLCNG@ zItht0j}m7dCC)rb9G6{78LM-9OYr1dIAvyultCvgx@u8BkuW}#9_k1LqOvcNp-hjn z&MAc`rCmcM-yu4Nz7gE<*4)7~5cuAn`R;mD{XOeiOc#Yl&ii0(0n_f{4EH3k4xMyb zGDWNS`X3-PC%SwcYH!*gj)@i=+J5L}8X!`w{yD;h8K@+r$Oy>QVA2UDOeEGfz0!y5 zluh(OvXH_Yd}C*PY-l<@I)mnvP3#GhUp5hYG(&A=6GK6Y%O>Iop^1a3Cxm&`1QPvB z2wfs4A(2w(EurQsorC#ayGUkxd}Rm9qoiytCkYLFYjzQ=7p{XsdHFJ-j+Ca*2zMx; z!kq}=j$w5T`hCuF5mPkB3u@&g1+hX-mx=p*X~Gcc*9b3#pF$GK+?wv4)raTPC`$}m z2BwqHge!#>Wq%|=GHm0lqxhKA(OBQ6OonxpX)Sbx5GpckN{+r%jW0^dZuK0Ta`mRJ zouB$TeRst8sbNusdav*!5MIkVJQ5CK9aSBGxh9m*W3(!|OP{!8GMACGc8; z8%dN4rVhBa!Edcz$ihi3`N*-wCRzi-%m7wA$2~*?cJ%hoSSr7VXW0LQ?Wb3s{sgDL zD&boXG$*9h$I}4g3+yrWndIy_(i(ml$8+_@ovytz9jYbPu(?yFquPXt$Zm$s=toS1 zy2Y^-3aW(^GqD}16Qn4_u`9NKO!REqWm=+l+bS^&u@#4Xh1c5pE#GB9&&y(nug0_* zKC!znzA7%d+VCi6h~v%IP} zWXwrK0teTw!A$e{Ot1tSD}1W?NHw~SZVyuEZAzhGRhZ1enXy|%=`R_rSL|oCzPz}~ zi53lUC^Sm;-ex#NB^xCQ0_q9n!H&*4jIJRTG)B}&w1@jrA|0Wy{Gg?LfzE1(yAU^ysX1A z9Odu?dbc%=SQk}R)`T{U#+tO{T`yNpfTqz61iW#oPEBa@=q9;~$MP<=)_*A1r^9QV zUwv^RZe?{i4(6`uSLf;`Prvbhb5>M!%kE?EI=R6QlhgtLZ@SRr7r+;OQ{;seW zc^^Bd2G=%^r=V2PM>|{s-RO;2mp`On*>nu;YF;!RXLKnYmBLYE1M{t>5;zVTO6T;_ z{3~rEjh?y|T*mop7ru!v^XgI6uiDjlSQS3iIFv@uLwl%_K!x;84XtC$ zL1To1PE#y&A3>|EnEENEcd;_J8MPs%U{T&s)b40KpAXlc{01%OWFK|7QItp2L#R!W z0o2VRtJ#B5|3;4Vwg-U@9~t8#1Zd?U;L)!@_efuks!`H^5kAzVV$U>6#B{6}LNHG}57J5?PQ$AN5Am2|a0wA| zPZiB_chyvz9t2$U0BWGd7pazK!%gF$To_JO$2{7Df>Q{jD@qAPS1)#J>s=E-eb-{J z07+mRc{vPbFi0CdsI~&R6UPuXSX*l8?y7#c-OFu@*4Bflgu#_$-?^|&1q=_3BQp&T zVin6VCI(B1*iGGtmLt;;R*$yXIc#pptpOuS03y^Rn0U^j~FJthem{h8kQ}W zcIwU3%B~uAR7-IlW`r4?s@Rb!Yx-Cd|~5-U_Kl z=D!909PUJ%dxG4X9kfBxUl6}Wcy9E;-GjkTz92m7ycH(?FcskQ?Bsh_rM|MJ-3cte8>{7%61OU0__r^0A3TCdd zUyo1(?RcC7wtQQlr~0GRxvbiG6Qo-!b$=k;pD!PwJi;jEH?6~}wjVF78@}!M#Ru?pxt9}Ww_iGSZu`X}5mV{=;?RhRpoV1@ zwN% zer|U4_RGdz?a7tP#)tzjyY0l8H+s>z>GTotAU?pb2BEqYVEg6S00$~-x1Bg%xd#pe z{q%2XIaJu$wV^c+AAFAU@OD@7SKChPs`PCVW|OyWzvxcqRq!~tS*XwPRmcZda^3dJ zckPCHe}w}UBIxWh+s`HMf}aProoIZc{};l;#y28P3tO`fY(Fyl#CF&0gLn^Y|6=y( z?MIS-*nVWrDKNR$DKc|;P7Tx=U@m9^`FLb@|0zVjpY1E~_p^Hqks;i+{c9X(L_Ze2$p2z}@tHfsGKcyes9ZT|gkQA2Xp8Pu*$(TAIc?S#U&AR;e_dasUhc>B z)WyDPf$M6E5({T8#dc$F|jV&P^)qS^XaipPuYZ}g~q}5|9&7qSt&}{=~ZryIc z`Z~UFKyCyqxX^7UGc>hOi-8ITTrXjdAW0R$gX)Vy>MhmFmY_?qAnByBz;;YvuJmBT+8yv0EN9iKS!7^5aX+Bac#1vf37 zEH2Q-`CmOhLj{AuS&Y8yK6I~R^kRC1WJ1Vy_SkbQgX8~bkI>%P#DFfatL9?jpO|nf zQp_!cKT2Kk5GBOt&SrIXo%m80+A+4SRlf-!-YoWoaTnGjCWlcM-d}wdE@HHfAv;o$ z@*>vqJOV*sKt4=x52`pIv8@65VTKGyxClicmTUdgy%j2CtGN7zE=PnH zFGERs;bySdts@47@4*_<&#!`xpmg=3w2hJ?aMTf0y%9WgmW~_fC}MC4BMa@!AR0E{ zpxncl7@*v5-5F<~ERjLRTECdT!mH43$f0mK)z+g4rxSqg98H+f25-u%4Zy- zjc`E-HAy4V*$6`R@Fz4F2_q|_KyYUiNh4-KYYUD%4o@ij!|Z=4Xqlg`hjGe+SIPRF%%B4-TC=Lw5*TN=~x zA(e1338X1!_7&2UhUI25L0pO>6XhvRt8>ONFzSNSqPIkEvlymsgJ9SpI{S$OkAV!r z(v_lPlA>Zb=m@8V#0RRY*3b|DJO06J>DzI9L8bICu^;EFMSp2nv6sHfeMaG$QNs$7 zb)ogKPcieZGeRWo7k4Ou}CAN4`#%exs>vf z!5g2VbRq@^h;;I`w(NIxP*=CQ=e!&nb-$mG>-^O%*NH-(*qerMT|X2nZB&Rv1&y0` zu~~9Y*|^VlKhR?4aQt%(LLYqfk927pgNY{kB@lCsmPX15Tc$3!GioV@|>> z5XP1+BW5CS2F}(>+9nWdh^@w|ie-zE0$=)VwcRjAF`<3zF-9?i1i@&fg2^C5+-{e# zQbEpkwXej;6n70ecUySLYV(22>eJHqYSquu4+kx6tv=z4E7Sup3`3Zz)%K*+;O)l# z0(-e}0I+wJ8wLUf)CC-{1k~Zcrrp&IT`=VCuP91Y_3@B^>VU0is6@Oe+R$0v`bK2HTjR+9-pF0XTV6-4G(&<$4zFy_o34!Rz1; z9LEWcd9}zo|0N`!EYFppv8e3O73?u*v7+guo;_6sQiQ)3Eq@=;p z(B^&ES%|uQ3pI6wzlK`})b+Sg9v&VAz?r~bAjnXGF3udR$_zLJR&4ig8fRUc|1 zDi9dq%#KVX08Z^9*U92hYJ5X71JG)t>B$U$Ffj5tY#wm)9@AM%kB>tQ+`kHyCJrS; zdKlip*t|yE@r9Tv)u%(yz)KsH?+&OqybLoL;JE5#>9-4WY?kT<-`D_B1SWOw>f_#R z6nv_WdqJW4G$w~?4IgfWIZMB4sCZW)CD5tRI~uAixZVecA=CJq!yxA$)I_9NNUzPcKL+rae$>EwiXwG?e+>aI%^EE z2XOKt&VtpUDmKLKK40U7bL0$L8atuWl$q<)CFmK*8Q;5JpxV*yhjzLv6yXZin&C~7`;2Oj zJ{oN$IqUeoxAWrpYQ3#9?sr^l5OTTd>#*zi{(@_kILXeu>>@Va7`B{hU+AwsBD5~M zMw`#?)ZykLDEk*0Qsx%>* z>Tsd+tLV55tHK5J)Q6${vsz!uxlNodIMZ65wF{kuCRj4ukPp82W}HOyMF-(mh_gA* z2$ofr^rpf>(&_3Pf3P%hE;T1+DyPI~5?RD-kXcL&S=Hm{Y(Ujs1n!v~_dn=m$LS=W zIU2r)zI~&k*1Ih3vsh=`T#}izl0#^qU>mi19$Hvpeu`+V)U0-h%*M5}Io38D$2+1c z3SS{6BK(9n59pnF&KdK`c%<+>48Lfp)!#9wcJ)LjbWVFjh!%%LOw_f2(l+Y&U#X5C zhSeuS*!!UkU;?Bnh!-?fNAW~o) zFO~T~vdX9kVaR^9;E@mH-~(~<#Nc7O3_8dnoG@82ZwW;cMF`yr-%nvbwB6`-acB(S z+(tlI3^b&EocJR|FjvX?TlT7ff{*A}qKiAR@);%iy{pY=IXahL-cdGs0o zvd5wG?d|Ly+0<) z!3^C71NAnf?5Q=PMY?-m-?u2c-&Lr1MXw4aG&xy`j=UFr@R!IKSKANBvZ~^jvf)}Wq?rsVG6T?SGb36N7 z=w^GkA0*sQheS4lt73T`Gv{H7r2-u>YQQ{0ej3h6*=tDCf$fMzZ@46`rT(fVxQFnK zmX4BTK1E$A2mM(6llkBp_tt)Q6pr$GkJWh-gbG9%N_67lLif!F4g?0{ZLFLbHg`!V zM+oB`fBGcgJ1@Eta`cDV)t@+vUYa?9mhd;7|@t|*svq;o~rFI zc&*a$VZy^6G@ zzSHp&x)?n}oj#$kr>|-4_K1E?Wfck{u9xC&NTy+k@2ICy)H~Bx6uuVupS2nyYVk9u zntVciAtmssN-mbI629_fHH(Ibf@ejWAo3fsyj;_?mRa@UvL9@B#UiW4d1lzb&oIWV zD!bJOBVuD&l3tQPbm@#F2NGxe>|fA#SbcO*@?s@Sevy)w=vGQom>VM{A1L{-sYj)f z9!h#v0^`V(5zQH1Z_K0mq1S(*ZkPrAG2@LEc&}-+N57mk^ldc!(K}}ipEeo+-Vh@Z zE_p#6u9I6hGJ_R$P*i_JuNfC-pc^Z^a>NH3&u{f{1O54}dIE7qhc(dW{8pnIc%9#B zas!>yTlBQChJ@Ry~roYZ@{x=M_>D#ALKpTvI< zS$e7k)8y5se3zPf5k%F`t}(54Qg2tIal(dge0P;66Ki*OMYuG>+q+soz48CNfYgP zE)_~B%{}dkH^kLf(uFYITOHAgJqfb`MOZ+lb=Baw*69q?pLbO#VPs60z-X?z!N0%q z#|wX;?$%*}81g8i4V&p)LOMf81O}~(LZ&y7Iy%wjr6*eYk>rYZCo%&znbmDDLzA!A znI$+&=|t0}23oUL#FD9yI>Q8?59E9sFKh)%Tg3N?3tN?DdFhsS z{0|PRD(#4n#N#dqxsa}?f5dn)Msm0jY8c!8j&j`6{}!W=YoT@_Amo&I-B5 zMw%6N?t`3oLV1jjn*P!iY&h`=%{*T(q8rzv%5tGT7Lx|cV|;A5WCN#dpdez&Vz0Wb zns`nz6oQc#N*Qvm)#5mApzfXHL=%pSqN`#cxr;>Tes`1As@?#pGcKDOTPn{DX|YRe6kzr z^wz|C?_Pc|!4Ov+j{uQDf6X*jvk%o_O8TI)`a(T6qu2JUgS}Ss5TJEz3&E_&r?}E& zS2S*7h^w`fj6{7?Z5slKcFXx{gZ((dE<|-LhEZucb74?IT6H2s3M8~`iFZ^RT_>=s z-1XjHGLT392|p0;Xx10>Jzwo->zui2rEZi@N9-hMf#f_6M)R}(g5Gs}b)&bvH_2=m zq&}oaN#A}3%gZ%AsvPw#HLGCA%XW*^sk27Ig*(um;AorJj(SY@JLwL%*p5c~7<}SH zqS%fh7%VudO}LQ3zBk^{fZZ*^6Wgw0SD^rLXScE zepL)x!^YiSg!cq#Ko>eI+8 zbSzF|g##^aqE&t6KU5ydOOPMYoybSG0@@*Jxlo=Q1vPTsirX8aurxx06){S&I+w2# zR$>ttXZad@Cpg9Nl2+#vrKAJdqFQWMP-Dm?M@qi3GfoWrMF~YXt#@MB<&HYSf?fP^ zV9pa2u&q{Zm$9J^fDCn*h*Dyddeg^O(0ec`_EA>nS~9#eI<0VFgFW1FUx<&rzca=Z zVe);v8;)};3ZZ2;b#46>l0pi24}C1mvjMGA-+oU-yNW_uW;Lm61xB)zS;G*xur`5H zH8{1f_gcKTei;&lzY@>k$q(swfEpDS(a(_M! z=@HGVIu|;B@lagPEu9_L#wNN0!m}INsETPxsbOrZp?3%G!h*JuZDm7crKgS)8CQ7m zQX6HkRISTGa1ul)k=Fln|-P77C%!VXt^LScpQT}qMHNpMra4g(Co`SIbS%@sOnWlbGi|;_+Iy4T z`IKor)1R2?hf8wO;4};_1wE0Mk)SCoxTCn#)w&CJTG38NbBz9j05D?qK>YQ1|BYncmBE71KAFZe#i-(^E|E<#ZL1#ZNw`-(5^6F^yvS5YvA% z)$HNi&k~wzR+j4|)ikc6s~V)a(mSO&v;ErUo4%Lk-YS;*+m5sQSGlJ_n$fw|vuU1} z2v1U)q?(zOC#AdjOpBS8GF`)TJ<|%NwM@Tc+Q3w0+Qd|OO1f`k+MTKOIjNq)w1nvz zrWH(kviUHkkxYj(jbb{QshMdkQ!CRXrgNC4FimHg#WaU$K2sahVx~)&mM~q$w3O*1 zOjj{o&2$aZ=a{Zz`U=zaOy6RFs)b%>3XKMOzW9`%T#6B%(Uk-(!DUIQB18& zvzV4L-Ndw>sq(C}AIUU{X))7vOzW5`obOCbBbjRXcJADHsf8KonAwoe+_`D#8EHAn z+`0Lwg=widt?GrgbYM+k?p#||HW^@rxpOm93vHfwQAV0AuRv3QyEz(|J2xYBUbg06 zVNrIbjr_9}j2I4DkWpwW$WF6q`u5z^ob36z8RD3pyAy>;^@70Q4@rOYjbWr6u?n1pRQl5qO8dXERBW(1Ms~B?_GFP7G>H} z=jCJ!gk5@47R|c~6XymhDYnJ=8F`tSF(oA@4;rRITdFNP%{1ShT97XEQiy4}^IOd= zqI3mUpl+vxV4W11ERtQYaJ7k)^o-0@dydVNmY1tpHKpa`71|3#SW_s?Qw#pyKD+N} zJ=-REPC;n(-|f1mwp(8Y|L_;UV%x= z7n**mEu%Qw=8^q9O~D;^=J_AJbDR6G0!Q)w3m^Z%{Wj&@mQ|3En(k5T6|Fuq_3dkd z^Q|~7Z*FQ)YBpD+ifKCP5mRhNVOl|Uert80n8s5z5tEZzSm>c^O0}6}li;auDJV!? z%zR`k)@&5*g0!r~9+rg(Q%v5%e5#R6Q|H}{mcW#NddtJ#CapbME8DYtrm0H4l8u^p zp;Cz7B7En9rYU(!fs%o*OsLGmSAo#aQEr5qd@1&d@=x{)mBnx|P04~iY5OL8MM}%~ zT_D`f#cz%>Ou5Oe-qu|O`o)S3Hf1=aF&bG+$SJhsAkEws@xkg73NkW;GUJIEwwb9p zcBw`htUk?Co%-IS9`DZ1F$KujDA?1$)$F|7xMH&Ee$Xskv!9N6dGXM+j@$76iy!&H zvY*~GNrta@x}-C57v$y@<(e{z(=zfYJ{%_XZ_Lj__V+Zq>8`f%rD-fNUb!l6SnU?i zKgA#8jdltgFe={dly+W5pSZX!ACOv)?eM(je z9^8PPUyMiZ2UR~sugByJz7_f>+e*8Vl7~x{mcRq=hf5xQsHC&6FML?Oe7UcpL`Ie* zm6Sp>GHhT}(yS8LfG0^wrQ}OkNofgeDexvMD@(8UD=x|}%Fq9c`(eo4cI4e01vvpv zSgt)MM}7)yLAfaudEADQln2u$l&MsF=Rh@G83o*5nGQ39g^pDBRLc=%GDDb4S7^n< z^GSqm8cJs#T*!mVHkg|UwL*j{6@HkM81%8iL2rXfF7&fehRC%9e9eQ+RJ@DbPX1GP z`>@F*;pco|f1!wl)R+KQDP|NeddL?l-5QQVInIZz41}-{dc+Y;kIYYiN&)PWtxWN~ z5Fu>ko#bE^bc$eVF{oXbCpYsHEj%XhBo!`5YKKo0W2urVe5M*rbJgFSBIH(vNC~n* zc5JA*h+dng>F_fRAs~(tZ~q!nPu~g=zcA!#E%uZq8L&nCC!g~0n+}zEaB;qe-&7ao zgR7L%o<2mt%p@fqxyOVMP%J6+skSzW@TB5f=CaldIYgm9oF6XU*4#8&q>3j8bK#fF zDU@#(;3b~tie5P^^Mhhk1+hx^(+gND`=kf~_cpj37-iJ`km!ad{?}D_)m50JKRnH2 zRj>_y{3`suS6lrLuEK#F60Aszr#(|@XS7x@0~|{SCu!&vlUERjeoRt9URp*W67;Qi z+t!E8SK$hSY;S9glAdIJjXrWt+HYbS*io7bW6Hr%65f;M>JCU6cu-RPDM@3Qc5c_! zpXpu_Ywl=N^IyZO*?kR9XRMi1IowrOgc>=LE+l-#A|SyJnW_d0$_y$@bezZoxK3*#8Z)RvH) z+gN=X(^;V8{%pMDH>EMTvk|l-$%@qG8kUa|86~ zfm&E0el$$_@l#?oDD<;)(*@25kBp3z*4)3F-jMm@eWn#mKW19RbQ{xJrhAxv$+Ur~ z%CwoO&zsVHBh#KtO-w_XhA|z?G?M9Xrcq2sGc_}fWol)b#59>{I@27cn)^1!OPDTW zx{B#Krt6tjFx|$qmT4W+2BytSbd(dyZnU&sU~p zjTCvKR6|5^mZu z7S79{`iCC4pGOFOcq)^BZfr?Zlq9_LBqk0`PanG2-N8%8GuV`f$3!2C@d$HP>!^hb zM^TF=pK0h%(AXdmtv=D|z!bJhX^@j>HtgNg}+G zQd(Gm=YvjzJ*xYsq8C6(F$H{B1R4u`i1x#g*0?$D}#{@cpcMC zOskmQ4Sq&|+thy3bFY|5h(UVDX93bBLYXYu^;Z4q$fxqje@E6I+Wf~R4Kp=wl=|zK zHZhIl`H{#?Qa_*RCZ? zF#lwq=5J^WpxL9@mz$V3ME=Qs4){x>4jO&jE5ahjY@Y7j_`k(H%DXg%rO;B0J$-2H zwfs|fF|YeERAO8 z;kWo2;ncG1f8(C?MaG!I+&TvTYno6_TLcp{tLizQNg>q6kec5_ugxz~%}3pt2Rk$~ z8UfA`kFHe<$eb+aI11c;QvcJ4_wOi;F*@PJq^Je+nu{`KVU&C$Ya=Ui$^Z1X3?uHV|Js^gim|%(hjViOt3OW{9#UHW zyf8-pZ~ip z@uinvdG)o|*T3=RhPU2+XXB=K-}}$|AAGpEqH@beAAj=cXI0f(Yqo9Qv9os9?$7t^ z-M7E)z`-xR{OapN^@qPX^6hs=k2M@W@%_mkPW`Aho_788^DnHc@6of@^}S7f`t}R$KVV?k4THi5 zM+_MndE-sPhTlBmmZ*`VqDPMzJIYd+(TY z=UsDC{$BsSCHFt@;Q#6P|DX2%f0_T5@iDP+6DC^YCrwUBoH8}(*4w5{$K7Yivu~ID z|9k!aPw-#4s`jH=POJaIuAxWzZU6o^)PTCB?myjs`Dis5(T8-ce_p!(?oYGt8JGVq zzMeMdTYruE|2iGHAL-WVtaQe=R5jvT`yx~SWrW$-)#m@Z*tVK7Dy`r0OQ&no2%HR< zPV>Cz{iH?QwQ~W@kK-|){@FI6?zzD)1QJU9y>i26ugs}Ab3+=|Q}CpJ@aJ&fZ3kCm z{`+EZbPjidjE7fctR6gm-XUFF-)FiG7&!F>m^6Dm;a6QYGqmUHm$uJeSX=n*!na)o zA_Ud@(g(hjwmamNU;E#8U~m4f#}@P+dwT6$>S!s-$=oY7d+!eXIP<37BZqi<#ogY& z`QSY>CvC0y1|2oyzlN;W$GrFElJ0#THSb$_E@0obWdj3TOKMj4e>?s<^0fPR-%NjD z&u6K#zumB5&d$JC-{0qV`S`$X8#iCRq5NMds|N```UI~0sOu|_Zk+kX3lrOocyIEZ z4RgOO@qhD5llkzY>+4FoFX)GfmGr2Do1WY`%kkCtNheeLzHxYrar7qz=YDw17CgHu z=(S!y3Jf_Cw%yKLw^~Vl=k#JyI$+kIO)-_A^NU) zx$)g|yAij|Hw`eq?UPh+{l%{itiIu+y?10jIVAd}Bllm*cx&3xqGw++ZJKuRvmG1y zub!Ovy^pdqqUQ2%b$xG33Nvgzcec~4PMybpd;gsyzka&g?B4{GW#_i(b7q z^~&b!?oLHd`eesm<@0N1ntHDuwOz!sM``E)C}wZhb7~ z*8RJl7&v3YiLVEqTL0W@??R9bk(pHY({D|s{T|%D&_6lP=d+laJ0qH{`i{T!oW1T+ zSkrHN$GtkF+q!$c{&+=f_Wioti^jDxyDzrCf|a%7?^TYrmp=4(mvMU+jC#N9=!)m{ z6XFuH0$)4w=VOnD9_#c|R^-6g_n-d7vh>56-CoIOZp}LUQvXHwm)|o5IVk<|DaY++ z4R6m{-SLAGCG&$DO#2URKXm`Xaj*7F+`RW_{@QQGSh{SUvb*k{HJwf#S~bc%^~AUh z7xRh|2b|jIFS_$L&e+iYU~=GVpASf#xx&%&?&{xG*cYj>gZEEdXL;UNziVu}E$h_U z{XfpAx^(=#SlzFi9x#O#_AfZF^kT)ycYK5W%=4n}oqFdh`-WLgOkKFp(C6&1nLo_z zRNeonL$0u8t|z}JijKejorvBOO;K+@d0^kAl}Gj-o$_#1){GbL%zm$I^mU>4#NCe} zho$GhNnLYiy)*BpH_Q6|`rYN1K0NvB@vdh-$sATv`u;6r2UmRd*wMGT>C=Dva?0H; zH+Q&u=KRi;N6fpU2Nh>Md((o$+c*CHadtYS50#U@%`LgS@xY9q*PA9i+dS*34gOc& z(cSmj;7Qp}e0Kc}J-6+A^WazYi*}AU5IEzT$5*Z0`ONyyC-)Co)D+adbo~XJvG=Gy zI$D2t`_Z)7#vea_O3gkrU~Iw3_aA<9)g66D4tvh-ckkrK8Ye~+UCe#vM{_`zZO#7< zG&apW+cBr6|9I!cU27i;DBQTD#dmo`%%*I6c*NjkvtBd)+Ph9M-@EgNc@^s2rS--i z*6gj_KBLSyG+6KFO8Wi;Z8-$;rF_j=uO}-n^n)hdXzuv%`-j zA053vxn)A%;3c!x&$j2U>kmFS-{Hxq z!pFA8yY@U4KKF@nQHJyXdCb)Lmf{1)#viCY+IMX5tYaHj&i&!{BIoWOU%unEq26Pr z{y3i2<`m<1zune8?#|=m-+yPw@c+D=_wwFxx%Y;>f1#*)a$W7g`3=qM-+S!{ ztj!5nyY>mC+Iq*Bm7h-fZN%Z*64fL3Z12TmKmOl&gPmWOzbA2k#f7M_&os@<(Y=a?JA; zubdoHxb^IWC2Qu{{$=RX?Z`VHzC7f;&Pi{UZy0%c{O~8AdgItJ-IXuS9W3!$yzUmb zu6y>mq%&`SyL0f{W1?z}=~`KXA@!QGm*Y5WEcKf5Zp1QGQX3eAPlZOr; zf4z^h^ADe0>GVg7uVHc8{69y0mANSCc;!4=yiVOTzq`NgVAXNoy6D2|j*J`f!pkSt zzL$F9+q6L+`wUq9>{t1zPxk+QYo$G)bDzPzdZ*mosp6&RgJp&HpGi4(X!nlrW7+L) zSy2)CbGyegUAghy@{W{0Zhy#j*NA6wx8JyV)rjrm!)hO0{7&dmnm|#~E!EGIe)aU= zn8V$FEO}|jnDYz9ERKID=8Yd0PdfEy-)Hi_ZuoWoZr?+%lz)<=q<{aPZ>PMPt6%@( z^Kt*O)lPr(k6Alxd%mox7@wPVc-EdfKDfE@N~fIETeo#TvgL^n)=oL|V$zQnzrE+d zG{5i{h8|tpm;6(VWuM$Ndgd)f-$%Xt`IxECA6z>4`3tYuc3!M`>c^gUN1dGe)^pBt zewThK{pzji+sE$PvvGO;xZZ2OSQw!OhTPvZZ*}%v#|J-}ZXNmJ6CE22!;?pMeSh_N z>%dQD4OlYd8>fBUs*>nVA0CNVW!=B<0iTnhPbUdKwI44er8oLPKJ68qPmrSXy-v}! z3sJnAS?}+IN|)Yu`0r*FL0F=O408 z=ijYT=imJcoqvyCbOAludj<6D?-kIC>Jgd{Qqgi52P(?Chjog1yg?~>5?c_D|L&{o zekM8+`ZxBY6?&q#(h@x=tZjQuIpg_oeX6YcQmzlkY^*5j8O!dNqHJPJ^&~wN zjJdz4)H0^5NlzVPnpdNzo-xT%(9^)Uy@ZO&Sk#wLYhq0Eg7h>q7X49D_)3PC=H2Mg zGZxtu*vMFudkkSrbA0re7xVMl#JE4>3dYp&pr?v4vZi=y8Eb+cb&LnGem&!G#tn=IGgcW# zFm7TzlyNiT5sa0uW%#MhqDRj-N{Ed#@Ng_lCgzx6yq4iX2w*; z=&>@UvPe%7krBF)H4oY z+@7%}ZxO&ajP(Nc_Tf=j5{*c)`hQQtgQ=oVqD7RJ2PIx zIGFKz#$6a!Fb-i{%eWikddA%utBiXvZf4w*vA$mNs~6)C#@90rW89l@6k`)(E8{+l zlNtAAoW-~w<6_34j7u5!XS{~-0LJSX4`f`y_y)$cj0Z8UXB^I0WjvU1Gvf%x#=|oH zLm8VG-^4hQ@o>gw#y2xgVmyLz3gcTC=QF0uKj|r9Jd*J$#-kXoV;s$R6XUUrs~C@C zT*uhVxPh^SaTDVh#>zJ`zOjr$7{@UVV?2Rz6yu4Et&HOtCo`VJIE(RQ#>I>i7?(1h z!gvkisf^b%rrS^HsbG8?<66cu8P_wO#aLy0JL6`?8I1KuWc(K}4q=?bIE?X9#!-xQ z+ySvN_F=5adq7ZMJjtxzo^cjqf5yd(0~wbxHZoqrxD(^`j5{-~VBCdqE#vNt>lycA z+{Ab=W93^J|7gY`jK?w#V;sXcitz%*NsM*ep-N%w!#JO@ALA0n?HR9P?9X@|<3Prn z7vz8RmPY|6;Cr`AIAFcWPJS?hcIr>IE*pf#zIdN<3Pq%#@!hwGmd7Q z#aPE3ykf>aj7u5&F&I^{TWv<4rE-*xI5!|#?g$M80)kS<53yk_Kb~;{TZ7W z2QrRi+?}zRaWvy(#yaj$W-)HhxR|j&<5I@m8Lwd+&3F@Ior$=O{vYG^CIOW?#{P^O zH2aL3H2a~_ymE}=%h<@+pRtK?G~-Cdx-ew?dPsguQrXiaX%c!!4o^~Pj^xDX$%Lc{J+$jZQj+pipexOh{0==Nmq!oD-_euH z?$Rz1dT76jq|!X?&Y_3oSm+_SEP4ugd|as9!+4RPB)LToNng=JvP1M_BS!SlE*E+V z-SLtBgSYG_mi2Fy)U3WxS$|e<+_sC~XnkLPP$|@JKJ>_js`ng`!CL zn*7uFOEQs^ewzQ3&m#~zZM;eOov_9~%J<}-7C*}W5iqTdXQ>=e{G{ZsEDwZl7p{H$x@P^5pWKGhGN`h{1yNA(4zi^iuk5zmRpQSzaB z#4{abebVZptXC+<%@!79{X#Cv9Ci=lL!24UDU&P8Z)>_P6zMd@&1YF3#Vfb6|FS-b zahDe<2i_V!lGi2QlG(hhPsY2`h1wmeue9xWkTToHfI`hm;W7IBa5+VibLT(VZd={uN%G0! z4!7i!#a(YnKDCxd$)^~1`70FpM8?xI95Vc^@zlbV#2l94N^qAO8LnyWaLRBcc!Y!U zTdTh^Toc{l*21Nwm#lBcdxUc#`Vg%pSNb38&Og%sM0Y))`QKU}lK-vwP5K|>uJ5G( zt>Gova>~6H{I*Sh84fLFWjGSu;gtN3cjrUtPm)_-`jg11OZ7J8vupc9HGq~wr9ac% z`A7QGTArjovF`YLy4yBgX@7KU%Ou^MijpKB>Fy*COnjK>j!z+a9;Bk>3~7J1TVL9b zan~anpSAj#>{Cj#wXfA8vY$!((CS}}e_DMj{hQ>Dr}S^SJ0D5&7I(Tz^9df|A>VIz z@Ar`AwbG`Qx5*y*WIoAVk7)7H+Ii`|#UuX2of+!Wk2aE{afAnL%Ofkn#2!s zZ<@Q^me}f%4@iHCN4rlr$sPaJe!gt!DPMR#+Ptw=?<+jdJ6JRHs~q>p-nQaihtjl- ztY6OxCdS_|j%55UV>9D*jFTAui*X9${fzS&f5o_j@j=F`7{9@I9pg&In;36nT*dfX z#&wLfb%F-Qdsx4T@n?*cCdrRl#@af_4UCPfkDEEf6UKM}V{P504`Vayr)m1Uj;3Rr z#QIu4Fop5`te?+#Gh=NXP+KP~WqqyuXzPGJY<>;vYyF$`j90L}wod5FxPtYydRkjI z)YcViS^p(AU(fh4#wug2UT$XmAJ*5Ok^J4jSX<}N`imi~?__;#ozjnS80%~6G}^kQ zp7oSX+l}&scv}^6voS5XRpz4r6?XaTMd-jIE46V4TdjnsFB6FBumzeuZ%< zN&iD ztgrt~riV7p2x0p}SwDpJZH%+nehA|**5AlDim}!|FtYg|*0-|$RL0u6br|Dh*1wza zI=0`NaTe>RG7e$=4vdRgpQqv6ata34FJ=8F7_VV`E8{SB&&YT^>ucj~J@Y4m^($Ck zYqi5z|3=oYW&L8t`K&*RaXsrl#5j`u@5p!!n;*wmW%Ff>n;B2j?6dis80&wR=__Z+ z6s4Bk@6Y-ntUsS|73+sG4r6^ei-vUp#*ebTCPxs?*vk3~87DJ#FfQfr3CUNeWwHLl ztY6If(Hds`WsFU%AIZ3s_17|9!&uJVK@Nwpo%Jgi&tM$&mE_NLjB8o{Nyhbzr!!U= zKgHP0_B%0dX8m%;NgUo0jP>WGe-ALWvi;7CLs);NW}o$g8Hcg{EXGlc*D$s+euQx{ z;};lbF#`+5~d^LT4O_VT@JQ zzn8Hlr($N@%=#-Cr?CAf#`oA1Tg z%KCFP%>LcXIGOdAFxKR*OpLQwe;(sC?EdwPi&;N~v6`yzXmxUbU;w>IG*u5DJmY=dpw5c5m7Ns z2LS~|m~d1yQ#4CTD>5r9pBm2RLzabRHkBHc6_)j&Sxst2W;P%yp1=RS)*cwm-lyk! zpZ9yO@4MQ1^SjsD>%8u@_MW}=tcg=rr|j+oWp&5yUaG9B*xk#N)f2lr5l{Oa({J7p zLzIV}c{rX=##0DK_d|D`r>CWk=jmyuquc(p+n)a#hx*dFw!@br}0<9j(jI^9R}wKpf(^)FPxn-z9HON37S-(*bda^_rNbcBZoQ=ihG^ z=f(O>ES`4T-Bb=r&(Yny4~cl5Mt>d8(=HEpy6x9N`}5%C%BquC52F3{#37#BMB^#3 zqkB?W>RW^oEnN=U3i!ew9Od^t_$+ zOtgom^$$u#9AZ5xp(#JHerR8Hrgdjp_pz_GigjpeZ+m*;Icu~1*xmMdNVw^*JwJL5 zo7iNJ;{0;#G9eDJ9@1Psv0l?WE@}On#;Lu3#X6MZc}kzgtvJNw)&wY_Yyh`Pth`tamhzSK+p=zKiwu{&<3JpSQ(2+LEUB66;>en#PA% zUurJDef`JoL+<{VJ?!l#*2$JN&EsNy(Y~rq>+7`sXkTro^=n$EYaWkcozT9jPUmU; z$(|dzX&$7%;t=awj_1XAYd%lzwf!v#@t%a( z^+Tj5*87^vBi3`8$B%F~*GIVNuRT7kgWBJ=u&*!E&NTM(VjaPr0zKEKYmz+$a?^T^ z<9TvBo~IVIm&m@Z(Cnu5blR20o}YdE&~DT8PJ=ij);a!oJrw>c(C+sNFZ>;^pJH9l zo;%f-?A2aca?`pm?LKWU50!yJh%2{PuWfE`u_KYAo7VsBZ>i9G9aQ^f`w{O0*xz2E z^JFjfw_n8fLi%Hlu+JajeF0vZi+10|MmvE$Jt0K!Jek@#}4m9 z`JQNUzc!=Jt=q&!O>W!m2dhHTMm4!99ZJ)l9yON}#BK&%vA;U`nfj8iIOtp&-1d8; zdp0p+8Lz%OkMc1dD_=%5JipgOG_Tt6Fz3{ejYPfsE1QXyhi+{lre!SHO0+F~=V_w( zQO{?I);)W+6ZO*LsiKw}H>1Cqv!xwiE&90%Z5-SdU^BQ-n z_L+&AAGc%@El#ttIG1$ECT95l#+jz7%ptcmbW<)-y?1gRQTM2SKGCxCBxm)t>;mpC zT3Se~F4PtgQ4UV_^1fnnXFRpOgsA>{bScsNkaHQ)7<-trqHIe!xh=unxqaArZsyGR z@-(Nm-`Lmbye=e*)9@?XO>T9>LQZS^yPSG$`!~qnT9C@A{pxE@bNQe*$-i29hEucg zS5EEf`?>uXx@2?e(ryXvnEMumw>|hKXT^6d-zGQnPvA7DKIGKqb*SY2Emm=A1Ha(Z zo$a-k{4GU{FOPy6~=PImq*r}_Pn2gtu-b_S=p_zb7T68atw|H3v-ZGS7L`GX(}Pk;JY zPQ$y2oN3-KaI&!XIMvf@Ic;Nn->2~Az!98j)r&YYmTu)#|NI80cEU;FPIvx*!dpxF zaaO+>$!U9S9cNln4rhht2xrDy7dg?7A5uKc(_;jy6FAc@XK)%+m7G@F_ng{8E(du% z*7xJA?l4oh&1(cb%$zLhFsF9gc}`uPAD_Rpd7+&8Z3{RvqBe8tyoxxDIbR5O_e-4B zPqsTu`RQw9&T8*C&a~hs1^bqBS_6)W^FKFo8Z$e7#M@)c7*5@h#hm7_t(@8y%Q@A@ zzv9%lXynwi=}<-SGJJKMmP;|5)(^IDvNgq=us=?-xt`PL*M`s670ZWnrtO~3$$s3x zslS@VX}o@jv*PDkPTRX)pHO~j>j!Wede7iAA6UU@lwRPp6;yI+Zk^!NN;d@)I)6&> zbsI)=8q;DpZHbR@R*cIRZnuv)EpMIU)XwleLh@PTRI;Sq*$f>P+R=9h;!I?Jk z7^gbw5+|G4<}-?K7%@z+WfUj-B9+s)?j=rhn|++s|2V;E^S#cgUD5t?ijQ%`slPdo z)6jDrr*+gWLEV1Nv_U61t9@?>|L@y>LGi0!8p&xMGmkUv_*zcmf|ofnj_&2O4LZ(g z-qgscw|Z6c{8zQ-)H?U&R6j9-Q|B^?)0#S$({?(Z(>NiOGtIn(Q(gZ8r*>o>r|scA zoccEpavIKjCC+=F=Ct&!=TwhX9i{wr5o*D?ojJ8p1320AFwW|*DV*l+^EizymU8M( zq;l#Cw{RMaFL0U%=5k_w;IzE(0cVADl+*V851hu;7dQ>>H#yDEdVNXd(YJ2LX?!Y} zQ_Y5PYL|`Ytp0o^C+oXNxHlL%b)T*0G_-z-QxowDr}>FOPV3&c1y3E~tZ+WYsqcJ> z(=y1$sgZAS8pnC_`?HEM?KrK&gE`fGhH~C<|Aun&;~CBFuKPWAhaK}7FgT&r>tS=g zTwm++{O4f_%NLCtck!#RInvPFa}P9xdA;}JsQ9r5!+diO8$NsDYS?Q7I%|>_-39Y%L zUVv1-r-iZi<3EHg&mQ{5-Z^S%`Z?E0Rg+swrTa5qJu$Y8)aTrlH}e;FlzzW)CDgk6 za#*^}csTNxi(zkkGQz_n#aYUKu-DJe7&}To?|o?Xt3Py>46dH}@AYz*L^;|>^A;>s zUmoTvO#R9yag)v z+hK`I4u2N*R&S|a@xlH5occ=R;=X&Ny6x4lSBJHjW6%XihW2ZBY(45NtuI{HE3UMy zRJic9LsQdzrA^hYhJ(IsrAGJrMh);7D2@2B@99JSZKao|ueh22PEYC4&DK57G~5h( z-fQYduMF%awf0^UUc2IISW?ts_x(pYNjoP+T>T}Wi&S~veRFkf2TDUiV|#4*% z+t!W+3p-0^j`zMgby9a}hj-T+%}zhb+SL~JTe}hW{d3YNsqIrM*44%aNP&x=tULeAU}^EH)t6q~ z9wt?qpR$b%A1?KbD!S0}yOGkqVNP?uJ2+hW=#yWqzg%o5mH719H1qQz(!RZYW}2oC zmm(ief4*0Un`E1O+KWBlt@L9rsd3wHZ}wn4B=eW`pKm>i`mOZz9uV9``Xqnu;EP5d zsfVth_4*BcrA|+D`7lX4SkkZDN|#S5w&Opy4XKbM(~ifwR;?N#oqqiLh{G+zr1Gf8 ztw(YOO0RlazRcP@QaU>CgZheGjr7vxd0h?Pgh|6}uRPZ3b$98sxjGw7ZlwGwf^4k3*!{^-n}^h!`QY+I8&qjoG2niGK!f8sM#!tUrv~QQf*O zEN||)mNDuclJ7qUwMre(Q~IJ&-R`*bTi9ctOGfm%2X>LX zFAYCldT^L@BkiF@o7)CU(G!3CXwi&wVZ#3IlhRjZ_MJLvn6xqM7i(^9AE~@c8{6TE zKGNcO-ob-#MSG!7nHtnVz!sOFA&*LM9NRKUdZPcC}DGg6NZu{Y-9@55&XI_8o$WW=O zpc7kue~8rit2VEu%fqBCeb)G>=M0uEtD?)+933g0y8Pj6b+)&3-F??0V=y*ET=HdF z=)MtB-Xpy~9Xzz3ityvgl$7mj^$nBcH9bkk;$&>qXtWXulYIs95_@N zM?tqXLPkkm+R-g-uZK!8?Z4SPW~?j?H4R_j5i>&iK4|ak$H#<7 zdB=CFwtq5I>hN5j5t~ALN!smgTc-Ptl5RyTf9hn|eNx+Bx<2vs1=#6#%QkPH+gsY& zYrw*(-}*=ghq(5s%dHCw8PzY{HfDmf;Qq_`uUzOOoiR*$bi?GH(ttH%PmS^EEvcrr zcGvWpBF+2an>GVdCrROpPpp}*A18JBZ0fmVg;S*cU$0vf_DFB3bv#0 zMlAdxF`^nPm!fQW7T#%%9!e@ioE)C~(Nt+pgLnRO&Qm1s zyxh;fd32JrZ_Xhf<2PE#^W9p%#E-{G0o_hN7t}9I8nbC(>n>_=;a^t$mrPCzOpJQ%m6Q)T6FC0JM`r~M6dxyGBedGH}>vFzL zuX7tAT~2wwf5=sx^k>g+G}g}ecU!i(-B4|?+?M^Wsfs#e-j+3u8#^~{ye;qf)?A{F zxh+35X40#1G-b+-52Zp%(jk8k|wms_&syy^|NBe&#VRr}VamEDq; zylpGK@7Y`O8U2g7?M%01(@(08N6fh;uV{C2&9ftJ$({HCT2=UeOMde4#?#;YbW{GM zBfjzb$xZp&jeA!a%5KU}Zs^ple)~;1q+ey^TdQx%yHi%CmCU~>cRV&4|8L6e=H1Bd z*XyQivLrpT$NQ#y_`PxSlj?8CUaz!xVs_09`R7knFDyTBLw@$h1uk#o+>pmSdHsQ& zTW`o6K6(iMZ^(~N^f|gC>V|A<{806b41e>0;d^@Ckgb-DE!~i72=~Hpn<>~Ef z-`qIuy8O9o&7|;Q*X84BiMpYh>vB%lvg$MS3PftaO+-*;Y<=l-ajady)+d3n3)Jk|1R@}wu@-A2#3Cb!EOjsMqV zbB9A)`t-XdJ564D^{Y1UH>>dfntXh)`uV5NUzPjEFKheSKd;J8nP;XQ{oty6LOttT z_p+<9cz$+OzHNEhXW(O3<&;YA#xusNa@p$BUhmAmDqDZ~=0-*MRrzecS2i3DxhnTQ zRXy>#=BoU4hRScc&sF(a;I3!hzuG9v!|K*XoNbi93e-NO`l?YLaT_;M?={Lr7nil# zThb`+dO~&C@=~KbsiN5S)8mcu-tCtCy-baA;N#Kw-zfXLrhdStH_G1~*uD0}QH`=W zXOn7DADrh$qwE@JNK?5s%DN4zn=TDkQZ(kt@T;cqP~dgY2-m6N)0%9B@QpTjTvy05t+zg`$}=H!wq^756c4?dZFMV`?1 zLDSLkSLCN|jXq=xxgziXe!_;}?pI`wb-R|Qw!R`8H+s#tI$e?58tU5^>MzS54I8`c z%8!@j=&Fr9^NwDY+a|u>Ch`5ta#*`;&xx;JmNQaj9vzi=S+-QCkGTI?oR8nwCVt~( zx!n@g(AOTiEMM(9W9j+m%X0Rlk&jJ}xGcwZ@tRvT=Cb^~<;lv~gD=avMXJ@;x?Prc zl-(EqhW};RwM~>^qswLaM*rR+v6nB&KL=D-&inb2oO|m0hf8WM$#c}t^-cfylKkwo z`w#EmcS*K9pjzfqa!D>a(ycD~AD86cqBd&3eEO1XaEVgQPP-(JSf(HK+e4S+QxWU- zy%=*z7SBsB$+tGWbGFC*m*ihtHH3Q%za&p~DVTk`&m}pa@zgoDj+f*^2Yr9++6r#H z3jZ(3uT1%F(K8ntmG~yH^?*QF6g;O(;)lv1M%Zp9^K@I z@U1Ik)%A8F5j*)^X`?^9Np(-&6OU zbh_I``K@1niCP_aQO-E3RR#H6lnqV?`&6ke%JBm;-`#wmPR_3%{>*W{ICR=(~ z>t(aPzsmD#u;0e1*FUb8eTO~p^wk6PvaYYH)cMVN+2#F<+dCB2%dh&K={@?@dO6p* za$d@F^>XWqbia!9dU@*SnW5Jnsh5ZJ+E}SyQ7`vWeY5V3_Sx#g zV`ja)z57J`ub1Z;+P?UY8s)2Q&jUN&}q{}Jaab@G(T4Wk;)*U6?zdF1b>>g0DP4OnuircUm-F{{+| zi#mC(wZo0Bhw9|szfI6hdZ$i4y{~@By4UOE7andGy*Iy3e%03+q*O^n#ysJgR41zj-OxW3TPOSUTDh>(ygK>g zuWx4Wolz%`{LHiE!tgq|`~4wLwUp~*->M<_UniGXetTkTzdCua_Q9W{y4A@&!}Ei* z?QotSb#j_ob>O-i+_qb4+bx@%)#Ht8zg@D)8N1FdyL8?r`x-XQay?^{UzqSrf#zGA zJmS?Mv&VgDlSiJ|KmDPPZSq5-w?3Y4vB_2c=yCq++ctU6`2i^d%WZPLI^J_*o=rA9 zTAugqKWuWOhibU|yiGn8xAKkRZ8mwcdW?IQ&2XQq?ETuqHu>#kM&zaiUFDJCDYHgu8#!+lNDKa=Qhc z_dKh$$-neoeIqv5Cc8U3ZPaOO^02ithIS7`cz)RAG;CDu>~5py7&z?2I(PDt`?T;G zi*G2=ceiHYn@RR}T`8nk?~unT{4lzpCfbSZ3~*xj?b<&J&Wl&! zaNgpeQzS~meo&u~u7gmzj)!Bu6^NropWrue9fRDV)0tp|u^&3si zj)y%A_gmz|GH@QY;~3-)ox&G5oUe4y?P=H}IOa>~-f>{5&Sp}v0r)v?Q}^Y3x!1N? z#WV+_bbh zU~EKW_o%U>p|YKK`aM8?<~#f*@^A(43&c4Qcj8zIXSp*Rk6U$zpPt99h955n&sQHs zajkcTi=u$Fclz=4Y4h?KN$`9^E%5TacQA+^aMkNAIX8Q0sit)7VS0P9wjoM`C%FD=sS=)$~Iw^Yu?76vD{DNQS0ZBSv8EB{zXq#N@Oab^C4 z)J9b+%HIR|yR%jiUPfNWMF{JpVieb2XP7Ya4RB`xCJz=6>&5~QKVXorH4WAP^I_+E zFgC9z7I85j&~X6z#}00#z4gAlUarh@kgL%i_cFo-;Kk=&O5Cx~r#ZMi+PSiJA+1=u zlr~m>V`~G&qg)N#yOM2YDP%=9A*8zh^rK zxS-AUu>e&iBbyRs5bdDyb7y{$kPz6ZAMDBKWuW@0I(V}Vv1-;KvK8wP(vo#Z3ACXF zs{JhJH)>=YDmN$Qrh0|92aiL3E>6q^bGORFiFpii(Ta4CR-_lw@njtaIi%CZleLNT zW^F>e$i{4HYfB5ypO?u4WkR_Ii86TvVjlR?Uu$oRZfMi7kY~{rM3q|$=C-MYp4&}F zZ`RS|#X81%vW}4+tRvFtI4H>0)*4_5>e_<&s@`HwXa}-QGv*A@XWr;DFV-preTI4j zVbeDCm)y-b~1uh@>PS4aoeOVx&T zu(h)WR=2VElg+gCWNjn;F!!ig+wHAw*hAUkWArxgvSDrs`ZB=Q+Ui>^e7*D@ye=qb ztQX3QdPRuy_BQ+)_4UNE5!HPh*at$iyW!n5{yltD%m;mJ@B3dCVDDcN)TRAGgu6!@ ztNdJwaW}?ipeGBA^=5&v$3T=TFv8zTHXYR0gSAt=%DikIRyT`_ z(b>>KYqt@rGxFdz>524Umx@h_d}&TX|6=^moD|f9Y+4PQZpGT9aGS0sn^2Z~8#u_{=4Zv2q)@r-BR_LfcT|8MAjI%B%Zx@ue%b1{h*91>o6FiuI1YHxHSu2whu0NPVXf6SL$xoS2?KZF=8uu&^^%k-il!K>* zG*B;pv@O5B(6uW6- zo+ztoD)X^t z`3aD|_IhEn$ks@&bp)S__+0^SBm4E`kM=D3&P&C-7Tr6KL*P-uB(`%BLgtL{8@)D9j>c94z3fJ=YlZL z@%c9e*AYHXqJ7YA0cf{??U=8+(DlD1+SrHrrHFZjY*|ZryQ!EP?84U{_iMOMv-gkP zo}W9-*ryPKVynJ)*?YUQ*kc~D)^5uka1UT=i+RDJ+$uj7U`1XOHv;$2{9YB~OBLR9 zuZr7!rt)@T-YG8k?8lT}8T%Gup9_3x?#Fe?Z?~wAy}au4*mD;0oV~pF7=IUV{yM~- zma452Yr9*0&wLMGz`Z%d9@f{_o%zC6eN%k!`i+Oh&FISS7g2oHrk=5SNQIL3s*8+m zh1lKqvc0EmjO9S=_NnTC`zPw_-FIGVg)QJ74gJ~y{n{a-*%pF&cwqh*&RVOqta%0z z_dax^6V#37UO(oaa_6TB`yB0i*Y(QYmfztgqx#`K{iwZfgK)2cyfHt}X9aywUpLk+ zwgLCf7ja#+F)zK6r`x^fr196$mNy*g%Jaqj7_QNDPZoqtusxB!C$2?QHXYjZZhIT* zr(#ne=j>(T*Dp7IzaP{M^MlDB#RDi);FMb*-YwH)NBRRy}@{=p6R58xnw1KM%O9ql;ykL?JP!2C~hOY>(! z@j1^E{e=4}Tn_>wd~No;SC3Y)1CXZqq?0G>gnsITe(JQntu4UX+Tv?e>-lG{MfbL| z%<(FA12RY{zi)d_^tCr@kA7@Fh^JBQYiY$lA5w39G)2X(K&II1OZUOJ-^HA8$EWY0 zU{Bb3AZu5Dr@!6Kg8F-~_K_J*?L(e+YOi|INz6=QOz8Y!6Zd(s&XF%Tbq?9#)LHek zQ}d@G5yn7aXl_(J$y}|@)lL@SuXe`g59A%(@EFsW9>=}y7N(9&hfO@eGjmLj9fZEYJ-wBWbJIN3 z{Q->g*-hizj7d*?&h!+Yb*bGgW3u_zcj5ch+sI`MS<8v1757jBnvlF$Mmdb#i z?Sp&U?qjP|>=49myW&0w_dmGC`(vKO9B%xt z=5uOOI{3XA^$*>nZ35f7v-T#~C+33omzN9*NXLyTriWM%hRE~rQ}XKy@=}ODCnpKk;`!j+2lv4BB^kjAyrfcY$%~?i!l;}lgO|d)DrPU|Fq5!4X!EB zFCc7}0ZvR0*#J2RX@v9~=)@j`?0|d@p_XuBEtoTNVXn*#&tE;*(u5@AN>l&U2}!2t zMFV0J6WOHbRnZHR;!F#dlClK9GQDCU543RH%H_+~h#z!b7?TngZLO&sa}exqK0RVSDk+l}2N7iYYD@zfqkK z6Sv~uN<<$WvnGPfb`cF3uF4kod1Nafmi{Wy*@=lxfr> z|4wdGqf^JMG?|i<#wM>!GW|O-DJxhFJ^!{JeFj6%P_cA8|F$2m!Y>@_1AlhVvvNAV zQFvBigF;tI6Gt0`iJ`(BrEro$Nnt;Q9Td7NY#b)y6)EW)P*|hzyh3-(m2~t}I6~nx zg-aD~R`{~Q3Wc92v?^>+=sHBC+fHGyLY+dr!gz&g3U?~BC_JL@gu+^d^$MMbihTSP z_Eb1R;UtAo3X>FWR=7i9mclZH`xG8lcv4}l!W#;`m3Ho=Fj!%TLY=}0g|Q0PD$Gz= zr0}4^V+zkIT&wge?=1W=D*d-W;UtBj3O6a-uFxLO-+B5<6K1(_af?MK6XU|8lVTI&;5T`E#I*5K1`pyBDJx*(;ueeflBdMjS*M7&B?&7`aVcXH zqgSjLpNcqrRY-?BCnhaUCa)E2ti$X|K5ru>K6i@L*!dQ#R~S(b(_-v`J9B)5J;zU- zBTR{7qm8DODRIgy%DyDODapypRvIU)Oo}ljBqxneGNFp>IGvfDkYZXHoj5CLbwW}s zV{f|5N{Z*zi5;IB6KAC06A}_pVthZ?WoDe|Pfxd}_P`XC5|_jqjv8&dOJrQi@`NN@ zDyRi`J8o_6{tWQX>3W0iAf2jgy_VCwQ)12pd-Q&g*}22 zaoboDmtrr;S3C_i#CCgbuEJ0Wr9ig=y} z8%FKsU}=L@=m3~N3W^dFoj5a&cM4f52P@B6VjZTW4 zX-rUN{2Z6a#1*3xQ7*>vNJm9xM<=d~WBKGl;e;!fTr>#S`;N9gJ{2_`h%(0{$HwWC zVRh(_h0YPt2`l27Y=p66)E4@f$Yg45YFC8%TC`%U7$A(zAUE%gHO4r$s<}nUkFGYz zX-%`Qh{p2|be)6dBBNfBoQPnHRO3?x!C5+;go@A8?$er+*zWuhrz%op)3HSgrCz5W2N8`P)5o|u*3ZtDRQt)>^cF7_n}{durnlawuGLus8N(>^40G=R^SVSKLqL0 zFM7;CDQIVtmDtUpxum4gE*Oh&mUiEW0jYH4KOaJSOVMwTP#xxQ-$)*M7WYp?oB_&i zJ6d$%oJg7;{%)_o-d>nnaL4qwef?GY!u+wtNG$(Q#-Xqt?#bLu% zvdzEk^RL^dXut8PCT@A;YCit9zrSjKXg_?!Xl^;7{oQ_lt^Mrn_IG>z-Cp9!&VTNC z3E9@1NM&XAt}*(zz5O+No0XJ^6)f>IFSs|oaUhPr+wZTnUm~6euE2h_6ZjKLdhRDK z6_X~?I~26umXe&97?+|f(lN~}@qNg&q($lB<&=TE#Qf!yf&Ar^ zfk-4WdQsB@915qOyfAX&)bYe9IEnaH3MW;M++$`%MoynLQ=|*`wCNF3M^9O(j~G95 z=B$YEP5zOir`dc;Ho8yz`u`m}|kr$pY{pE3{bJ)81( z3?DgX;!MQ&YxSe}f2AELy}Nst{OGu6{AM$DSVj`z?`0>maSGoD=K=ogd~^O0Sj@#b zti!4yMdarbk_S$l-h5(I0v2kO9fSvE{m}oVzKnhLpytcP$LP%|@eY;>ap_Hnfg1dA zIPB-`UKGX~;tsj{oIQ+6W7qz(bV9mR&j0k`gm)IjTOj}al=XYW_uuX1w3AYH)c;jZ z;q&hO{d#-6|Lybka@c7#+`GKhZ;STO?-jJ=3EJcRRa)nY^fTrQj{U0uJvi01qlCs_K9qbIQ@se z8*d3Oox(kTG5lyH{96eBMb??DcI>CD+AO}!!yS1vrN2It$C%-QmEjW$PH5P|6H8AF zx+5$(Hhxv}z4a=-+p*pr^AAD(Lmp!1i+lYQ8EF3W_zxA}e>s`xMKQ5)i zycWJV8R`6e{D0m8YJ67k$JfcQ$4VO54SSYkK-Ypc$WG|=U4;>C87rZESim(93-k=| z3Z#m{;JXVNNHui&9>XqtpP>dieZS!_qyaj8#~}vaX}AHMzT04dXxhOaT!HUAXra@0 zA695!m!yL(xbZPSSA!XlMCfMFvnyk%B7O`hHLibmC`_TIkha-yYa&1iB84hS;FTg4pbrharA~{XQDte*~P~3*X6akMC)K zj|QWh&^LqcLv}$w2sT2FK_`CP8)-p50{#G@e68TaebCPEUkCmI*$@3Z_#i$bRY5m^ zKK-z#7W7fz90Cgdf2v#Wm#Dj`X^udHh zk!KK1~_rJD9;oy4nqF%pk{>VYhp5l{EgstMc)CQfKXjdf~`ji z-5)$R3T=VB&V%=b3I7mqt)j03KT!08V1OjrFc4e=Asbr<7L$&2O2BiF9OyT|WLfmD z5ge|AeZYSdNN=`lI->mGRfv_sgHy-AwxR36Cm~ekZQu_O>N_h~51~3XfUaYO?ha}o z6rLEW=)}3>7#oGW=7Yx}dgwNA$V7~B=%L_32&HKNpMX$0JHf9YlEzP{yOjxh)n~3aB>Xl0{Ig69?s z{XDoGk8)^??*PN%mGKU?OhCR!M-5geI&s`m;ZOWY(QCmq%P{s3ejQi`ISIW1OiC2G z5%gNFj0JE;650o0EMWg+#`{qV9)*}t=40S$BgP^0RPYIWAHly1+8@j?Aw1#|&#n^T zYr)ZLggy?egi!ksr>_-ls0TlMSm+1Ad5;KvK6r7x&>KL*M%W?JCr;dqGE*CZnl0#S z`0oV$(?wl~pFe@ULf~HwmThJ12*Ou@rfrPZi`ecd%-itq0RH$i+64L;uslQP72wuq zMVi~duG>ZVyMsSNXe`%)?>>igk(UKLyaVGA=~RJlJdg1U|2M%oFNnT~0&haL!JoZ| z-;3HQ#68e0wI2(Nr{TMhqN7zXe_zEN# z{=2{nkPzrLFfvz!AwCPCaasU&%oqL|FbYzI@WcTH*xL{KD6kQ-3p#O3p=gJ3;A@J0 z1bn7g)FlJ_4MKfw16P&e`yhy$3Z8*bzQl87cqZ5tb1B%roS}g+#=%Jt8edbu#flye zniahOJPqlG@MpmOuZwv=3oeCphrb0p4WV>u!A3|Zj&IS z6N43<_@JU2K%=4)Ga!28TLQLwQ>5Pkoc%V!!9NQ0Jbd9ztbE1B(>B1PuNNeb)_r2fk5-aR~iQunI!q zZJ*-#@DbP{g#njC%+QVC9!M1QO0ex`bT5cD2Y-Tehh7W1e=f>Hyb0+CfA$5|Mj%v| zc(4#cbuR&vs)fH1Y@qqrU&1UsJ+<%tJ}ofhQ}1s{UYJ%b561u-HW8`$X#(uJ-8y?(-+3*8%ZJ1eeJ z?%-I+PWX=lH$$jRwt-WB7W04}+zUB|F#Exi5b8%_hgwliVlIT*ssQZxi_kUTedk1) zAz%gMB+?;9{;JGB;04GH_}jqh^Jupo=oe7^8$MG&CoYFHAdC?_2ch})JUHxv$V&(2 zL8xp6;7EKPs8R-75 zDmENK;dg=q+_1+8bS-$FyNcWJDDWtR%5V%U@>KDAtrGAWgxco@I0<`Bkv;`{SQpQTx*q%q zLiSk;hK`19z@KP^oP-`cM#WA+=>Gf+ICrebcRsjaJo*%Sq-+M&6VQ*)iJ1@$^c-++ zIP!(QA3QJ-wgTM(9-k!ili-vos2BW+L#DzFJroSotJpf|j{C3BzAWW4kzXGt&d;ES zK(7Y(M2fzv1ZU5}*oMCW)Xi0~anOnPKcHeJ=;7dbNGx>XTl2)Ys07c=M|;A*0Ze~T z#R{MkUxQGa7l1!1`WbM+0+Fu)3}1+PQTpH-2!-DTu8v0f@J|JILZ}~!KPh@G7`jOK zvltaCjz?UCF9Bb|{wxj9cY#Beiareo%OEt?D!{?ZgdPIE0O^k3NZkp31qp{vd#{X4 z#=auZcNsATA3`~x$ET>+HxMdM4H&ip^@YCdDUJLt#ZUL(y zI_T<$Rcsg}9C|1?em&|1JseyGp>$Hg=OMhELH`ZtYlI;VhLC>(b5H|yS2SRbJ;C-9W7VxM2Zr*^<=imLH%Vx}{@YjGpLoCqQW0+eY2J{#0 zw@drxYLO0+_QEBdNPE=ofKH@6THO(zNPDLGBYh(6mrCJ@v`?uS{zTfNl>CXb$0zAT z+GmY)BJGJrI&r+B6KO9x@+ZMB2ZMbRz8=Mmpa=EM4fdXBX{t zMgBzEdxvx)?KMO?k@gfKok;rxkxqN$yrJl{7miiY*?;%Qz#djeBP}aEt01c)t1_!P zt0v2u6_~Bb)@Fxh>$1bM_1RI`hV1xkV|HqGT6TJNM)uBZb9O;?MRsMjCA%uSI=d#@ znq8Z1%WlkOIqo^?oWLASPH>JkCp1Tw6P}~biOMnL#OD}uQghOB(sMF$cIKFK3UVrP zDswD3RXNo;H96Lt+8kR>V-Cx8&sFCJ=4x_-bG5mlxw_o&Tzzg-t|2!**O;4{o0glN zn~}RS*PL6BTajCtYssz3tm``y_phYR~F5}%LtVP+HOJ>V!%wzfP`Re?@d`*6E zzBWHJUzZ=Aug{OlH{{3X8}n21)AG~vGxB%loAV3uEAlJzE%{aX)%i8~*8JLhTYh6c zD{wDR7X%h)3W5u?1)&AH0)0VLfuSJ2z*vx4kXDdhkWsL+z+6yJP*G4>U@537s4l1} zuol!7*b4AzxzN2(T^LxXDGV;u7KRq;3d0Nag;9lu!uUdCVQOJoVR~Uk;m$&HVL_p> zG_^FXG`+OCw5HTrT3c!>Z7gMF?q%w-z%or)aGAC&v`kkPUZyXLDl?SDmobZq;j+o% z^HTHD^LFMHsS#G@YR zs6zqDUxl*QqTKE%b1=#qjoH_u|0f;NsBY@ZzZA_~O*!^x~bx1;v%cRmC;MwZ)Ca?j?aG z!6l(3;U!Te@g=Dx=_NZ$3Q8(Vs!D3m|BWT?rGcfvrJ<$arBS8vXqWWTouvh(m8Df^ zn_9GsJK7`|?GcW)h%ZYmOE245R!~-1Rt3AMh2^-H2bKqyhn9zzN0rByrs49pD9 z49yJBjLMA9OwCNs+?iRBS(#atS(90t*_i2`6_^#A6`B>E6_pjAm5R~16XUZos|usD zHmfnqJv%Tv7~?Y>V>2G(G96>G0OPR=W3d+F&>dqi7~?M-V=o@#E*)czS#iBUzgo=I zW~QrY)0Ysk1a$+ALj`KFg40%u373 z$TDM&uwag`X4$e>wwlipI?NCT%noUo8O)d!ESM3jm<RBiw+|oraNZ#;CSnL|ZYM8Ah@OqgaO#Y{)a_rR8Phne!_0EP2&= z);t?VpcbV5C)Jl-V%C)EHe_KC%oLRcRPeW{f5aMv@hyh+zb2 zFnV+tIR=cHG>jN?VMU>(u)5G%Xe(qz>LN{%wn$f`FESJvi_(fRip)h7MV6xKB5RSY zh!v}gHO1OuU9rB{P;4wtE6yl37grQpimQvQ#dtzhqAt;tXiIb@`VvElu_Ub|qr_ZN zQDP~n#%yFOVWsL)O{un2SE?^Hlo~NcGfK^+6{Qx8R4Ya)!wA)2bm}lN4Q0l%w6csc zb6G{11+$p7%vQ$KxOT(VXpXJoGbkK-JdXiFbFGEJ7)9|IK?N8)HjEvHaic57NdvCW rKsBtO7Ix4GOK^uR1i~7~CUvk%J?zo|%QV6sYG4Tn{`c|!VGH~pa0P#$ literal 0 HcmV?d00001 diff --git a/natives/libjinput-linux64.so b/natives/libjinput-linux64.so new file mode 100644 index 0000000000000000000000000000000000000000..0512c928dd2494f2ccc3b6af736684b710eb7e22 GIT binary patch literal 18432 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 literal 0 HcmV?d00001 diff --git a/natives/libjinput-osx.jnilib b/natives/libjinput-osx.jnilib new file mode 100644 index 0000000000000000000000000000000000000000..4d90199fa813d826061b20669cc9aec59f563ba6 GIT binary patch literal 23620 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 literal 0 HcmV?d00001 diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 0000000..76f079f --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.samples.dir + Must set src.core.dir + Must set src.jutils.dir + Must set src.awt.dir + Must set src.linux.dir + Must set src.windows.dir + Must set src.osx.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/configs/nativeStart.properties b/nbproject/configs/nativeStart.properties new file mode 100644 index 0000000..0ccad37 --- /dev/null +++ b/nbproject/configs/nativeStart.properties @@ -0,0 +1,2 @@ +main.class=samples.TestSample +run.jvmargs=-Djava.library.path=natives diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 0000000..bd3b420 --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=4c1e7187 +build.xml.script.CRC32=8ddf4329 +build.xml.stylesheet.CRC32=8064a381@1.80.1.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=7abf3dc5 +nbproject/build-impl.xml.script.CRC32=0847958b +nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48 diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..c7477ab --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,84 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=jInput +application.vendor=Herve +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/jInput.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.external.vm=true +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir}:\ + ${libs.junit_4.classpath}:\ + ${libs.hamcrest.classpath} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class=net.java.games.input.Version +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=true +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.samples.dir=src\\samples +test.run.args=-Djava.library.path=D:/Java/jInput/natives +src.awt.dir=src\\plugins\\awt +src.core.dir=src\\core +src.jutils.dir=src\\jutils +src.linux.dir=src\\plugins\\linux +src.osx.dir=src\\plugins\\osx +src.windows.dir=src\\plugins\\windows +test.src.dir=test diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..09f3db9 --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,21 @@ + + + org.netbeans.modules.java.j2seproject + + + jInput + + + + + + + + + + + + + + + diff --git a/src/core/net/java/games/input/AbstractComponent.java b/src/core/net/java/games/input/AbstractComponent.java new file mode 100644 index 0000000..90fef20 --- /dev/null +++ b/src/core/net/java/games/input/AbstractComponent.java @@ -0,0 +1,145 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** + * Skeleton implementation of a named axis. + */ +public abstract class AbstractComponent implements Component { + + /** + * Human-readable name for this Axis + */ + private final String name; + + private final Identifier id; + + private boolean has_polled; + private float value; + private float event_value; + + /** + * Protected constructor + * @param name A name for the axis + */ + protected AbstractComponent(String name, Identifier id) { + this.name = name; + this.id = id; + } + + /** + * Returns the type or identifier of the axis. + */ + public Identifier getIdentifier() { + return id; + } + + /** + * Returns whether or not the axis is analog, or false if it is digital. + * @return false by default, can be overridden + */ + public boolean isAnalog() { + return false; + } + + /** + * Returns the suggested dead zone for this axis. Dead zone is the + * amount polled data can vary before considered a significant change + * in value. An application can safely ignore changes less than this + * value in the positive or negative direction. + * @return 0.0f by default, can be overridden + */ + public float getDeadZone() { + return 0.0f; + } + + /** + * Returns the data from the last time the control has been polled. + * If this axis is a button, the value returned will be either 0.0f or 1.0f. + * If this axis is normalised, the value returned will be between -1.0f and + * 1.0f. + * @return The data from the last time the control has been polled. + */ + public final float getPollData() { + if (!has_polled && !isRelative()) { + has_polled = true; + try { + setPollData(poll()); + } catch (IOException e) { + ControllerEnvironment.log("Failed to poll component: " + e); + } + } + return value; + } + + final void resetHasPolled() { + has_polled = false; + } + + final void setPollData(float value) { + this.value = value; + } + + final float getEventValue() { + return event_value; + } + + final void setEventValue(float event_value) { + this.event_value = event_value; + } + + /** + * Returns a human-readable name for this axis. + */ + public String getName() { + return name; + } + + /** + * Returns a non-localized string description of this axis. + */ + public String toString() { + return name; + } + + protected abstract float poll() throws IOException; + +} // AbstractAxis diff --git a/src/core/net/java/games/input/AbstractController.java b/src/core/net/java/games/input/AbstractController.java new file mode 100644 index 0000000..5b12a8e --- /dev/null +++ b/src/core/net/java/games/input/AbstractController.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +import java.util.Map; +import java.util.HashMap; + +import java.io.IOException; + +/** + * An AbstractController is a skeleton implementation of a controller that + * contains a fixed number of axes, controllers, and rumblers. + */ +public abstract class AbstractController implements Controller { + final static int EVENT_QUEUE_DEPTH = 32; + + private final static Event event = new Event(); + + /** + * Human-readable name for this Controller + */ + private final String name; + + /** + * Array of components + */ + private final Component[] components; + + /** + * Array of child controllers + */ + private final Controller[] children; + + /** + * Array of rumblers + */ + private final Rumbler[] rumblers; + + /** + * Map from Component.Identifiers to Components + */ + private final Map id_to_components = new HashMap<>(); + + private EventQueue event_queue = new EventQueue(EVENT_QUEUE_DEPTH); + + /** + * Protected constructor for a controller containing the specified + * axes, child controllers, and rumblers + * @param name name for the controller + * @param components components for the controller + * @param children child controllers for the controller + * @param rumblers rumblers for the controller + */ + protected AbstractController(String name, Component[] components, Controller[] children, Rumbler[] rumblers) { + this.name = name; + this.components = components; + this.children = children; + this.rumblers = rumblers; + // process from last to first to let earlier listed Components get higher priority + for (int i = components.length - 1; i >= 0; i--) { + id_to_components.put(components[i].getIdentifier(), components[i]); + } + } + + /** + * Returns the controllers connected to make up this controller, or + * an empty array if this controller contains no child controllers. + * The objects in the array are returned in order of assignment priority + * (primary stick, secondary buttons, etc.). + */ + public final Controller[] getControllers() { + return children; + } + + /** + * Returns the components on this controller, in order of assignment priority. + * For example, the button controller on a mouse returns an array containing + * the primary or leftmost mouse button, followed by the secondary or + * rightmost mouse button (if present), followed by the middle mouse button + * (if present). + * The array returned is an empty array if this controller contains no components + * (such as a logical grouping of child controllers). + */ + public final Component[] getComponents() { + return components; + } + + /** + * Returns a single component based on its identifier, or null + * if no component with the specified type could be found. + */ + public final Component getComponent(Component.Identifier id) { + return id_to_components.get(id); + } + + /** + * Returns the rumblers for sending feedback to this controller, or an + * empty array if there are no rumblers on this controller. + */ + public final Rumbler[] getRumblers() { + return rumblers; + } + + /** + * Returns the port type for this Controller. + * @return PortType.UNKNOWN by default, can be overridden + */ + public PortType getPortType() { + return PortType.UNKNOWN; + } + + /** + * Returns the zero-based port number for this Controller. + * @return 0 by default, can be overridden + */ + public int getPortNumber() { + return 0; + } + + /** + * Returns a human-readable name for this Controller. + */ + public final String getName() { + return name; + } + + /** + * Returns a non-localized string description of this controller. + */ + public String toString() { + return name; + } + + /** Returns the type of the Controller. + */ + public Type getType() { + return Type.UNKNOWN; + } + + /** + * Creates a new EventQueue. Events in old queue are lost. + */ + public final void setEventQueueSize(int size) { + try { + setDeviceEventQueueSize(size); + event_queue = new EventQueue(size); + } catch (IOException e) { + ControllerEnvironment.log("Failed to create new event queue of size " + size + ": " + e); + } + } + + /** + * Plugins override this method to adjust their internal event queue size + */ + protected void setDeviceEventQueueSize(int size) throws IOException { + } + + public final EventQueue getEventQueue() { + return event_queue; + } + + protected abstract boolean getNextDeviceEvent(Event event) throws IOException; + + protected void pollDevice() throws IOException { + } + + /* poll() is synchronized to protect the static event */ + public synchronized boolean poll() { + Component[] components = getComponents(); + try { + pollDevice(); + for (int i = 0; i < components.length; i++) { + AbstractComponent component = (AbstractComponent)components[i]; + if (component.isRelative()) { + component.setPollData(0); + } else { + // Let the component poll itself lazily + component.resetHasPolled(); + } + } + while (getNextDeviceEvent(event)) { + AbstractComponent component = (AbstractComponent)event.getComponent(); + float value = event.getValue(); + if (component.isRelative()) { + if (value == 0) + continue; + component.setPollData(component.getPollData() + value); + } else { + if (value == component.getEventValue()) + continue; + component.setEventValue(value); + } + if (!event_queue.isFull()) + event_queue.add(event); + } + return true; + } catch (IOException e) { + ControllerEnvironment.log("Failed to poll device: " + e.getMessage()); + return false; + } + } + +} // class AbstractController diff --git a/src/core/net/java/games/input/Component.java b/src/core/net/java/games/input/Component.java new file mode 100644 index 0000000..02fbcd5 --- /dev/null +++ b/src/core/net/java/games/input/Component.java @@ -0,0 +1,782 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** + * An axis is a single button, slider, or dial, which has a single range. An + * axis can hold information for motion (linear or rotational), velocity, + * force, or acceleration. + */ +public interface Component { + + /** + * Returns the identifier of the axis. + */ + public abstract Identifier getIdentifier(); + + /** + * Returns true if data returned from poll + * is relative to the last call, or false if data + * is absolute. + */ + public abstract boolean isRelative(); + + /** + * Returns whether or not the axis is analog, or false if it is digital. + */ + public abstract boolean isAnalog(); + + /** + * Returns the suggested dead zone for this axis. Dead zone is the + * amount polled data can vary before considered a significant change + * in value. An application can safely ignore changes less than this + * value in the positive or negative direction. + * @see #getPollData + */ + public abstract float getDeadZone(); + + /** + * Returns the data from the last time the control has been polled. + * If this axis is a button, the value returned will be either 0.0f or 1.0f. + * If this axis is normalized, the value returned will be between -1.0f and + * 1.0f. + * @see Controller#poll + */ + public abstract float getPollData(); + + /** + * Returns a human-readable name for this axis. + */ + public abstract String getName(); + + /** + * Identifiers for different Axes. + */ + public static class Identifier { + + /** + * Name of axis type + */ + private final String name; + + /** + * Protected constructor + */ + protected Identifier(String name) { + this.name = name; + } + + /** + * Returns a non-localized string description of this axis type. + */ + public String getName() { + return name; + } + + /** + * Returns a non-localized string description of this axis type. + */ + public String toString() { + return name; + } + + public static class Axis extends Identifier { + + /** + * @param name + */ + protected Axis(String name) { + super(name); + } + + /** + * An axis for specifying vertical data. + */ + public static final Axis X = new Axis("x"); + + /** + * An axis for specifying horizontal data. + */ + public static final Axis Y = new Axis("y"); + + /** + * An axis for specifying third dimensional up/down + * data, or linear data in any direction that is + * neither horizontal nor vertical. + */ + public static final Axis Z = new Axis("z"); + + /** + * An axis for specifying left-right rotational data. + */ + public static final Axis RX = new Axis("rx"); + + /** + * An axis for specifying forward-back rotational data. + */ + public static final Axis RY = new Axis("ry"); + + /** + * An axis for specifying up-down rotational data + * (rudder control). + */ + public static final Axis RZ = new Axis("rz"); + + /** + * An axis for a slider or mouse wheel. + */ + public static final Axis SLIDER = new Axis("slider"); + + /** + * An axis for slider or mouse wheel acceleration data. + */ + public static final Axis SLIDER_ACCELERATION = new Axis("slider-acceleration"); + + /** + * An axis for slider force data. + */ + public static final Axis SLIDER_FORCE = new Axis("slider-force"); + + /** + * An axis for slider or mouse wheel velocity data. + */ + public static final Axis SLIDER_VELOCITY = new Axis("slider-velocity"); + + /** + * An axis for specifying vertical acceleration data. + */ + public static final Axis X_ACCELERATION = new Axis("x-acceleration"); + + /** + * An axis for specifying vertical force data. + */ + public static final Axis X_FORCE = new Axis("x-force"); + + /** + * An axis for specifying vertical velocity data. + */ + public static final Axis X_VELOCITY = new Axis("x-velocity"); + + /** + * An axis for specifying horizontal acceleration data. + */ + public static final Axis Y_ACCELERATION = new Axis("y-acceleration"); + + /** + * An axis for specifying horizontal force data. + */ + public static final Axis Y_FORCE = new Axis("y-force"); + + /** + * An axis for specifying horizontal velocity data. + */ + public static final Axis Y_VELOCITY = new Axis("y-velocity"); + + /** + * An axis for specifying third dimensional up/down acceleration data. + */ + public static final Axis Z_ACCELERATION = new Axis("z-acceleration"); + + /** + * An axis for specifying third dimensional up/down force data. + */ + public static final Axis Z_FORCE = new Axis("z-force"); + + /** + * An axis for specifying third dimensional up/down velocity data. + */ + public static final Axis Z_VELOCITY = new Axis("z-velocity"); + + /** + * An axis for specifying left-right angular acceleration data. + */ + public static final Axis RX_ACCELERATION = new Axis("rx-acceleration"); + + /** + * An axis for specifying left-right angular force (torque) data. + */ + public static final Axis RX_FORCE = new Axis("rx-force"); + + /** + * An axis for specifying left-right angular velocity data. + */ + public static final Axis RX_VELOCITY = new Axis("rx-velocity"); + + /** + * An axis for specifying forward-back angular acceleration data. + */ + public static final Axis RY_ACCELERATION = new Axis("ry-acceleration"); + + /** + * An axis for specifying forward-back angular force (torque) data. + */ + public static final Axis RY_FORCE = new Axis("ry-force"); + + /** + * An axis for specifying forward-back angular velocity data. + */ + public static final Axis RY_VELOCITY = new Axis("ry-velocity"); + + /** + * An axis for specifying up-down angular acceleration data. + */ + public static final Axis RZ_ACCELERATION = new Axis("rz-acceleration"); + + /** + * An axis for specifying up-down angular force (torque) data. + */ + public static final Axis RZ_FORCE = new Axis("rz-force"); + + /** + * An axis for specifying up-down angular velocity data. + */ + public static final Axis RZ_VELOCITY = new Axis("rz-velocity"); + + /** + * An axis for a point-of-view control. + */ + public static final Axis POV = new Axis("pov"); + + /** + * An unknown axis. + */ + public static final Axis UNKNOWN = new Axis("unknown"); + + } + + public static class Button extends Identifier { + + public Button(String name) { + super(name); + } + + /** First device button + */ + public static final Button _0 = new Button("0"); + + /** Second device button + */ + public static final Button _1 = new Button("1"); + + /** Thrid device button + */ + public static final Button _2 = new Button("2"); + + /** Fourth device button + */ + public static final Button _3 = new Button("3"); + + /** Fifth device button + */ + public static final Button _4 = new Button("4"); + + /** Sixth device button + */ + public static final Button _5 = new Button("5"); + + /** Seventh device button + */ + public static final Button _6 = new Button("6"); + + /** Eighth device button + */ + public static final Button _7 = new Button("7"); + + /** Ninth device button + */ + public static final Button _8 = new Button("8"); + + /** 10th device button + */ + public static final Button _9 = new Button("9"); + public static final Button _10 = new Button("10"); + public static final Button _11 = new Button("11"); + public static final Button _12 = new Button("12"); + public static final Button _13 = new Button("13"); + public static final Button _14 = new Button("14"); + public static final Button _15 = new Button("15"); + public static final Button _16 = new Button("16"); + public static final Button _17 = new Button("17"); + public static final Button _18 = new Button("18"); + public static final Button _19 = new Button("19"); + public static final Button _20 = new Button("20"); + public static final Button _21 = new Button("21"); + public static final Button _22 = new Button("22"); + public static final Button _23 = new Button("23"); + public static final Button _24 = new Button("24"); + public static final Button _25 = new Button("25"); + public static final Button _26 = new Button("26"); + public static final Button _27 = new Button("27"); + public static final Button _28 = new Button("28"); + public static final Button _29 = new Button("29"); + public static final Button _30 = new Button("30"); + public static final Button _31 = new Button("31"); + + /** Joystick trigger button + */ + public static final Button TRIGGER = new Button("Trigger"); + + /** Joystick thumb button + */ + public static final Button THUMB = new Button("Thumb"); + + /** Second joystick thumb button + */ + public static final Button THUMB2 = new Button("Thumb 2"); + + /** Joystick top button + */ + public static final Button TOP = new Button("Top"); + + /** Second joystick top button + */ + public static final Button TOP2 = new Button("Top 2"); + + /** The joystick button you play with with you little finger (Pinkie on *that* side + * of the pond :P) + */ + public static final Button PINKIE = new Button("Pinkie"); + + /** Joystick button on the base of the device + */ + public static final Button BASE = new Button("Base"); + + /** Second joystick button on the base of the device + */ + public static final Button BASE2 = new Button("Base 2"); + + /** Thrid joystick button on the base of the device + */ + public static final Button BASE3 = new Button("Base 3"); + + /** Fourth joystick button on the base of the device + */ + public static final Button BASE4 = new Button("Base 4"); + + /** Fifth joystick button on the base of the device + */ + public static final Button BASE5 = new Button("Base 5"); + + /** Sixth joystick button on the base of the device + */ + public static final Button BASE6 = new Button("Base 6"); + + /** erm, dunno, but it's in the defines so it might exist. + */ + public static final Button DEAD = new Button("Dead"); + + /** 'A' button on a gamepad + */ + public static final Button A = new Button("A"); + + /** 'B' button on a gamepad + */ + public static final Button B = new Button("B"); + + /** 'C' button on a gamepad + */ + public static final Button C = new Button("C"); + + /** 'X' button on a gamepad + */ + public static final Button X = new Button("X"); + + /** 'Y' button on a gamepad + */ + public static final Button Y = new Button("Y"); + + /** 'Z' button on a gamepad + */ + public static final Button Z = new Button("Z"); + + /** Left thumb button on a gamepad + */ + public static final Button LEFT_THUMB = new Button("Left Thumb"); + + /** Right thumb button on a gamepad + */ + public static final Button RIGHT_THUMB = new Button("Right Thumb"); + + /** Second left thumb button on a gamepad + */ + public static final Button LEFT_THUMB2 = new Button("Left Thumb 2"); + + /** Second right thumb button on a gamepad + */ + public static final Button RIGHT_THUMB2 = new Button("Right Thumb 2"); + + /** 'Select' button on a gamepad + */ + public static final Button SELECT = new Button("Select"); + + /** 'Start' button on a gamepad + */ + public static final Button START = new Button("Start"); + + /** 'Mode' button on a gamepad + */ + public static final Button MODE = new Button("Mode"); + + /** Another left thumb button on a gamepad (how many thumbs do you have??) + */ + public static final Button LEFT_THUMB3 = new Button("Left Thumb 3"); + + /** Another right thumb button on a gamepad + */ + public static final Button RIGHT_THUMB3 = new Button("Right Thumb 3"); + + /** Digitiser pen tool button + */ + public static final Button TOOL_PEN = new Button("Pen"); + + /** Digitiser rubber (eraser) tool button + */ + public static final Button TOOL_RUBBER = new Button("Rubber"); + + /** Digitiser brush tool button + */ + public static final Button TOOL_BRUSH = new Button("Brush"); + + /** Digitiser pencil tool button + */ + public static final Button TOOL_PENCIL = new Button("Pencil"); + + /** Digitiser airbrush tool button + */ + public static final Button TOOL_AIRBRUSH = new Button("Airbrush"); + + /** Digitiser finger tool button + */ + public static final Button TOOL_FINGER = new Button("Finger"); + + /** Digitiser mouse tool button + */ + public static final Button TOOL_MOUSE = new Button("Mouse"); + + /** Digitiser lens tool button + */ + public static final Button TOOL_LENS = new Button("Lens"); + + /** Digitiser touch button + */ + public static final Button TOUCH = new Button("Touch"); + + /** Digitiser stylus button + */ + public static final Button STYLUS = new Button("Stylus"); + + /** Second digitiser stylus button + */ + public static final Button STYLUS2 = new Button("Stylus 2"); + + /** + * An unknown button + */ + public static final Button UNKNOWN = new Button("Unknown"); + + /** + * Returns the back mouse button. + */ + public static final Button BACK = new Button("Back"); + + /** + * Returns the extra mouse button. + */ + public static final Button EXTRA = new Button("Extra"); + + /** + * Returns the forward mouse button. + */ + public static final Button FORWARD = new Button("Forward"); + + /** + * The primary or leftmost mouse button. + */ + public static final Button LEFT = new Button("Left"); + + /** + * Returns the middle mouse button, not present if the mouse has fewer than three buttons. + */ + public static final Button MIDDLE = new Button("Middle"); + + /** + * The secondary or rightmost mouse button, not present if the mouse is a single-button mouse. + */ + public static final Button RIGHT = new Button("Right"); + + /** + * Returns the side mouse button. + */ + public static final Button SIDE = new Button("Side"); + + /** + * Extra, unnamed, buttons + */ + public static final Button EXTRA_1 = new Button("Extra 1"); + public static final Button EXTRA_2 = new Button("Extra 2"); + public static final Button EXTRA_3 = new Button("Extra 3"); + public static final Button EXTRA_4 = new Button("Extra 4"); + public static final Button EXTRA_5 = new Button("Extra 5"); + public static final Button EXTRA_6 = new Button("Extra 6"); + public static final Button EXTRA_7 = new Button("Extra 7"); + public static final Button EXTRA_8 = new Button("Extra 8"); + public static final Button EXTRA_9 = new Button("Extra 9"); + public static final Button EXTRA_10 = new Button("Extra 10"); + public static final Button EXTRA_11 = new Button("Extra 11"); + public static final Button EXTRA_12 = new Button("Extra 12"); + public static final Button EXTRA_13 = new Button("Extra 13"); + public static final Button EXTRA_14 = new Button("Extra 14"); + public static final Button EXTRA_15 = new Button("Extra 15"); + public static final Button EXTRA_16 = new Button("Extra 16"); + public static final Button EXTRA_17 = new Button("Extra 17"); + public static final Button EXTRA_18 = new Button("Extra 18"); + public static final Button EXTRA_19 = new Button("Extra 19"); + public static final Button EXTRA_20 = new Button("Extra 20"); + public static final Button EXTRA_21 = new Button("Extra 21"); + public static final Button EXTRA_22 = new Button("Extra 22"); + public static final Button EXTRA_23 = new Button("Extra 23"); + public static final Button EXTRA_24 = new Button("Extra 24"); + public static final Button EXTRA_25 = new Button("Extra 25"); + public static final Button EXTRA_26 = new Button("Extra 26"); + public static final Button EXTRA_27 = new Button("Extra 27"); + public static final Button EXTRA_28 = new Button("Extra 28"); + public static final Button EXTRA_29 = new Button("Extra 29"); + public static final Button EXTRA_30 = new Button("Extra 30"); + public static final Button EXTRA_31 = new Button("Extra 31"); + public static final Button EXTRA_32 = new Button("Extra 32"); + public static final Button EXTRA_33 = new Button("Extra 33"); + public static final Button EXTRA_34 = new Button("Extra 34"); + public static final Button EXTRA_35 = new Button("Extra 35"); + public static final Button EXTRA_36 = new Button("Extra 36"); + public static final Button EXTRA_37 = new Button("Extra 37"); + public static final Button EXTRA_38 = new Button("Extra 38"); + public static final Button EXTRA_39 = new Button("Extra 39"); + public static final Button EXTRA_40 = new Button("Extra 40"); + } + + /** + * KeyIDs for standard PC (LATIN-1) keyboards + */ + public static class Key extends Identifier { + /** + * Protected constructor + */ + protected Key(String name) { + super(name); + } + + /** + * Standard keyboard (LATIN-1) keys + * UNIX X11 keysym values are listed to the right + */ + public static final Key VOID = new Key("Void"); // MS 0x00 UNIX 0xFFFFFF + public static final Key ESCAPE = new Key("Escape"); // MS 0x01 UNIX 0xFF1B + public static final Key _1 = new Key("1"); // MS 0x02 UNIX 0x031 EXCLAM 0x021 + public static final Key _2 = new Key("2"); // MS 0x03 UNIX 0x032 AT 0x040 + public static final Key _3 = new Key("3"); // MS 0x04 UNIX 0x033 NUMBERSIGN 0x023 + public static final Key _4 = new Key("4"); // MS 0x05 UNIX 0x034 DOLLAR 0x024 + public static final Key _5 = new Key("5"); // MS 0x06 UNIX 0x035 PERCENT 0x025 + public static final Key _6 = new Key("6"); // MS 0x07 UNIX 0x036 CIRCUMFLEX 0x05e + public static final Key _7 = new Key("7"); // MS 0x08 UNIX 0x037 AMPERSAND 0x026 + public static final Key _8 = new Key("8"); // MS 0x09 UNIX 0x038 ASTERISK 0x02a + public static final Key _9 = new Key("9"); // MS 0x0A UNIX 0x039 PARENLEFT 0x028 + public static final Key _0 = new Key("0"); // MS 0x0B UNIX 0x030 PARENRIGHT 0x029 + public static final Key MINUS = new Key("-"); // MS 0x0C UNIX 0x02d UNDERSCORE 0x05f + public static final Key EQUALS = new Key("="); // MS 0x0D UNIX 0x03d PLUS 0x02b + public static final Key BACK = new Key("Back"); // MS 0x0E UNIX 0xFF08 + public static final Key TAB = new Key("Tab"); // MS 0x0F UNIX 0xFF09 + public static final Key Q = new Key("Q"); // MS 0x10 UNIX 0x071 UPPER 0x051 + public static final Key W = new Key("W"); // MS 0x11 UNIX 0x077 UPPER 0x057 + public static final Key E = new Key("E"); // MS 0x12 UNIX 0x065 UPPER 0x045 + public static final Key R = new Key("R"); // MS 0x13 UNIX 0x072 UPPER 0x052 + public static final Key T = new Key("T"); // MS 0x14 UNIX 0x074 UPPER 0x054 + public static final Key Y = new Key("Y"); // MS 0x15 UNIX 0x079 UPPER 0x059 + public static final Key U = new Key("U"); // MS 0x16 UNIX 0x075 UPPER 0x055 + public static final Key I = new Key("I"); // MS 0x17 UNIX 0x069 UPPER 0x049 + public static final Key O = new Key("O"); // MS 0x18 UNIX 0x06F UPPER 0x04F + public static final Key P = new Key("P"); // MS 0x19 UNIX 0x070 UPPER 0x050 + public static final Key LBRACKET = new Key("["); // MS 0x1A UNIX 0x05b BRACE 0x07b + public static final Key RBRACKET = new Key("]"); // MS 0x1B UNIX 0x05d BRACE 0x07d + public static final Key RETURN = new Key("Return"); // MS 0x1C UNIX 0xFF0D + public static final Key LCONTROL = new Key("Left Control"); // MS 0x1D UNIX 0xFFE3 + public static final Key A = new Key("A"); // MS 0x1E UNIX 0x061 UPPER 0x041 + public static final Key S = new Key("S"); // MS 0x1F UNIX 0x073 UPPER 0x053 + public static final Key D = new Key("D"); // MS 0x20 UNIX 0x064 UPPER 0x044 + public static final Key F = new Key("F"); // MS 0x21 UNIX 0x066 UPPER 0x046 + public static final Key G = new Key("G"); // MS 0x22 UNIX 0x067 UPPER 0x047 + public static final Key H = new Key("H"); // MS 0x23 UNIX 0x068 UPPER 0x048 + public static final Key J = new Key("J"); // MS 0x24 UNIX 0x06A UPPER 0x04A + public static final Key K = new Key("K"); // MS 0x25 UNIX 0x06B UPPER 0x04B + public static final Key L = new Key("L"); // MS 0x26 UNIX 0x06C UPPER 0x04C + public static final Key SEMICOLON = new Key(";"); // MS 0x27 UNIX 0x03b COLON 0x03a + public static final Key APOSTROPHE = new Key("'"); // MS 0x28 UNIX 0x027 QUOTEDBL 0x022 + public static final Key GRAVE = new Key("~"); // MS 0x29 UNIX 0x060 TILDE 0x07e + public static final Key LSHIFT = new Key("Left Shift"); // MS 0x2A UNIX 0xFFE1 + public static final Key BACKSLASH = new Key("\\"); // MS 0x2B UNIX 0x05c BAR 0x07c + public static final Key Z = new Key("Z"); // MS 0x2C UNIX 0x07A UPPER 0x05A + public static final Key X = new Key("X"); // MS 0x2D UNIX 0x078 UPPER 0x058 + public static final Key C = new Key("C"); // MS 0x2E UNIX 0x063 UPPER 0x043 + public static final Key V = new Key("V"); // MS 0x2F UNIX 0x076 UPPER 0x056 + public static final Key B = new Key("B"); // MS 0x30 UNIX 0x062 UPPER 0x042 + public static final Key N = new Key("N"); // MS 0x31 UNIX 0x06E UPPER 0x04E + public static final Key M = new Key("M"); // MS 0x32 UNIX 0x06D UPPER 0x04D + public static final Key COMMA = new Key(","); // MS 0x33 UNIX 0x02c LESS 0x03c + public static final Key PERIOD = new Key("."); // MS 0x34 UNIX 0x02e GREATER 0x03e + public static final Key SLASH = new Key("/"); // MS 0x35 UNIX 0x02f QUESTION 0x03f + public static final Key RSHIFT = new Key("Right Shift"); // MS 0x36 UNIX 0xFFE2 + public static final Key MULTIPLY = new Key("Multiply"); // MS 0x37 UNIX 0xFFAA + public static final Key LALT = new Key("Left Alt"); // MS 0x38 UNIX 0xFFE9 + public static final Key SPACE = new Key(" "); // MS 0x39 UNIX 0x020 + public static final Key CAPITAL = new Key("Caps Lock"); // MS 0x3A UNIX 0xFFE5 SHIFTLOCK 0xFFE6 + public static final Key F1 = new Key("F1"); // MS 0x3B UNIX 0xFFBE + public static final Key F2 = new Key("F2"); // MS 0x3C UNIX 0xFFBF + public static final Key F3 = new Key("F3"); // MS 0x3D UNIX 0xFFC0 + public static final Key F4 = new Key("F4"); // MS 0x3E UNIX 0xFFC1 + public static final Key F5 = new Key("F5"); // MS 0x3F UNIX 0xFFC2 + public static final Key F6 = new Key("F6"); // MS 0x40 UNIX 0xFFC3 + public static final Key F7 = new Key("F7"); // MS 0x41 UNIX 0xFFC4 + public static final Key F8 = new Key("F8"); // MS 0x42 UNIX 0xFFC5 + public static final Key F9 = new Key("F9"); // MS 0x43 UNIX 0xFFC6 + public static final Key F10 = new Key("F10"); // MS 0x44 UNIX 0xFFC7 + public static final Key NUMLOCK = new Key("Num Lock"); // MS 0x45 UNIX 0xFF7F + public static final Key SCROLL = new Key("Scroll Lock"); // MS 0x46 UNIX 0xFF14 + public static final Key NUMPAD7 = new Key("Num 7"); // MS 0x47 UNIX 0xFFB7 HOME 0xFF95 + public static final Key NUMPAD8 = new Key("Num 8"); // MS 0x48 UNIX 0xFFB8 UP 0xFF97 + public static final Key NUMPAD9 = new Key("Num 9"); // MS 0x49 UNIX 0xFFB9 PRIOR 0xFF9A + public static final Key SUBTRACT = new Key("Num -"); // MS 0x4A UNIX 0xFFAD + public static final Key NUMPAD4 = new Key("Num 4"); // MS 0x4B UNIX 0xFFB4 LEFT 0xFF96 + public static final Key NUMPAD5 = new Key("Num 5"); // MS 0x4C UNIX 0xFFB5 + public static final Key NUMPAD6 = new Key("Num 6"); // MS 0x4D UNIX 0xFFB6 RIGHT 0xFF98 + public static final Key ADD = new Key("Num +"); // MS 0x4E UNIX 0xFFAB + public static final Key NUMPAD1 = new Key("Num 1"); // MS 0x4F UNIX 0xFFB1 END 0xFF9C + public static final Key NUMPAD2 = new Key("Num 2"); // MS 0x50 UNIX 0xFFB2 DOWN 0xFF99 + public static final Key NUMPAD3 = new Key("Num 3"); // MS 0x51 UNIX 0xFFB3 NEXT 0xFF9B + public static final Key NUMPAD0 = new Key("Num 0"); // MS 0x52 UNIX 0xFFB0 INSERT 0xFF9E + public static final Key DECIMAL = new Key("Num ."); // MS 0x53 UNIX 0xFFAE DELETE 0xFF9F + public static final Key F11 = new Key("F11"); // MS 0x57 UNIX 0xFFC8 + public static final Key F12 = new Key("F12"); // MS 0x58 UNIX 0xFFC9 + public static final Key F13 = new Key("F13"); // MS 0x64 UNIX 0xFFCA + public static final Key F14 = new Key("F14"); // MS 0x65 UNIX 0xFFCB + public static final Key F15 = new Key("F15"); // MS 0x66 UNIX 0xFFCC + public static final Key KANA = new Key("Kana"); // MS 0x70 UNIX 0xFF2D + public static final Key CONVERT = new Key("Convert"); // MS 0x79 Japanese keyboard + public static final Key NOCONVERT = new Key("Noconvert"); // MS 0x7B Japanese keyboard + public static final Key YEN = new Key("Yen"); // MS 0x7D UNIX 0x0a5 + public static final Key NUMPADEQUAL = new Key("Num ="); // MS 0x8D UNIX 0xFFBD + public static final Key CIRCUMFLEX = new Key("Circumflex"); // MS 0x90 Japanese keyboard + public static final Key AT = new Key("At"); // MS 0x91 UNIX 0x040 + public static final Key COLON = new Key("Colon"); // MS 0x92 UNIX 0x03a + public static final Key UNDERLINE = new Key("Underline"); // MS 0x93 NEC PC98 + public static final Key KANJI = new Key("Kanji"); // MS 0x94 UNIX 0xFF21 + public static final Key STOP = new Key("Stop"); // MS 0x95 UNIX 0xFF69 + public static final Key AX = new Key("Ax"); // MS 0x96 Japan AX + public static final Key UNLABELED = new Key("Unlabeled"); // MS 0x97 J3100 + public static final Key NUMPADENTER = new Key("Num Enter"); // MS 0x9C UNIX 0xFF8D + public static final Key RCONTROL = new Key("Right Control"); // MS 0x9D UNIX 0xFFE4 + public static final Key NUMPADCOMMA = new Key("Num ,"); // MS 0xB3 UNIX 0xFFAC + public static final Key DIVIDE = new Key("Num /"); // MS 0xB5 UNIX 0xFFAF + public static final Key SYSRQ = new Key("SysRq"); // MS 0xB7 UNIX 0xFF15 PRINT 0xFF61 + public static final Key RALT = new Key("Right Alt"); // MS 0xB8 UNIX 0xFFEA + public static final Key PAUSE = new Key("Pause"); // MS 0xC5 UNIX 0xFF13 BREAK 0xFF6B + public static final Key HOME = new Key("Home"); // MS 0xC7 UNIX 0xFF50 + public static final Key UP = new Key("Up"); // MS 0xC8 UNIX 0xFF52 + public static final Key PAGEUP = new Key("Pg Up"); // MS 0xC9 UNIX 0xFF55 + public static final Key LEFT = new Key("Left"); // MS 0xCB UNIX 0xFF51 + public static final Key RIGHT = new Key("Right"); // MS 0xCD UNIX 0xFF53 + public static final Key END = new Key("End"); // MS 0xCF UNIX 0xFF57 + public static final Key DOWN = new Key("Down"); // MS 0xD0 UNIX 0xFF54 + public static final Key PAGEDOWN = new Key("Pg Down"); // MS 0xD1 UNIX 0xFF56 + public static final Key INSERT = new Key("Insert"); // MS 0xD2 UNIX 0xFF63 + public static final Key DELETE = new Key("Delete"); // MS 0xD3 UNIX 0xFFFF + public static final Key LWIN = new Key("Left Windows"); // MS 0xDB UNIX META 0xFFE7 SUPER 0xFFEB HYPER 0xFFED + public static final Key RWIN = new Key("Right Windows"); // MS 0xDC UNIX META 0xFFE8 SUPER 0xFFEC HYPER 0xFFEE + public static final Key APPS = new Key("Apps"); // MS 0xDD UNIX 0xFF67 + public static final Key POWER = new Key("Power"); // MS 0xDE Sun 0x1005FF76 SHIFT 0x1005FF7D + public static final Key SLEEP = new Key("Sleep"); // MS 0xDF No UNIX keysym + public static final Key UNKNOWN = new Key("Unknown"); + } // class StandardKeyboard.KeyID + + } // class Axis.Identifier + + /** + * POV enum for different positions. + */ + public static class POV { + /** + * Standard value for center HAT position + */ + public static final float OFF = 0.0f; + /** + * Synonmous with OFF + */ + public static final float CENTER = OFF; + /** + * Standard value for up-left HAT position + */ + public static final float UP_LEFT = 0.125f; + /** + * Standard value for up HAT position + */ + public static final float UP = 0.25f; + /** + * Standard value for up-right HAT position + */ + public static final float UP_RIGHT = 0.375f; + /** + * Standard value for right HAT position + */ + public static final float RIGHT = 0.50f; + /** + * Standard value for down-right HAT position + */ + public static final float DOWN_RIGHT = 0.625f; + /** + * Standard value for down HAT position + */ + public static final float DOWN = 0.75f; + /** + * Standard value for down-left HAT position + */ + public static final float DOWN_LEFT = 0.875f; + /** + * Standard value for left HAT position + */ + public static final float LEFT = 1.0f; + } // class Axis.POV +} // interface Axis diff --git a/src/core/net/java/games/input/Controller.java b/src/core/net/java/games/input/Controller.java new file mode 100644 index 0000000..4d96964 --- /dev/null +++ b/src/core/net/java/games/input/Controller.java @@ -0,0 +1,263 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** + * A Controller represents a physical device, such as a keyboard, mouse, + * or joystick, or a logical grouping of related controls, such as a button + * pad or mouse ball. A controller can be composed of multiple controllers. + * For example, the ball of a mouse and its buttons are two separate + * controllers. + */ +public interface Controller { + + /** + * Returns the controllers connected to make up this controller, or + * an empty array if this controller contains no child controllers. + * The objects in the array are returned in order of assignment priority + * (primary stick, secondary buttons, etc.). + */ + public abstract Controller[] getControllers(); + + /** + * Returns the type of the Controller. + */ + public abstract Type getType(); + + /** + * Returns the components on this controller, in order of assignment priority. + * For example, the button controller on a mouse returns an array containing + * the primary or leftmost mouse button, followed by the secondary or + * rightmost mouse button (if present), followed by the middle mouse button + * (if present). + * The array returned is an empty array if this controller contains no components + * (such as a logical grouping of child controllers). + */ + public abstract Component[] getComponents(); + + /** + * Returns a single axis based on its type, or null + * if no axis with the specified type could be found. + */ + public abstract Component getComponent(Component.Identifier id); + + /** + * Returns the rumblers for sending feedback to this controller, or an + * empty array if there are no rumblers on this controller. + */ + public abstract Rumbler[] getRumblers(); + + /** + * Polls axes for data. Returns false if the controller is no longer valid. + * Polling reflects the current state of the device when polled. + */ + public abstract boolean poll(); + + /** + * Initialized the controller event queue to a new size. Existing events + * in the queue are lost. + */ + public abstract void setEventQueueSize(int size); + + /** + * Get the device event queue + */ + public abstract EventQueue getEventQueue(); + + /** + * Returns the port type for this Controller. + */ + public abstract PortType getPortType(); + + /** + * Returns the zero-based port number for this Controller. + */ + public abstract int getPortNumber(); + + /** + * Returns a human-readable name for this Controller. + */ + public abstract String getName(); + + /** + * Types of controller objects. + */ + public static class Type { + + /** + * Name of controller type + */ + private final String name; + + /** + * Protected constructor + */ + protected Type(String name) { + this.name = name; + } + + /** + * Returns a non-localized string description of this controller type. + */ + public String toString() { + return name; + } + + /** + * Unkown controller type. + */ + public static final Type UNKNOWN = new Type("Unknown"); + + /** + * Mouse controller. + */ + public static final Type MOUSE = new Type("Mouse"); + + /** + * A keyboard controller + */ + public static final Type KEYBOARD = new Type("Keyboard"); + + /** + * Fingerstick controller; note that this may be sometimes treated as a + * type of mouse or stick. + */ + public static final Type FINGERSTICK = new Type("Fingerstick"); + + /** + * Gamepad controller. + */ + public static final Type GAMEPAD = new Type("Gamepad"); + + /** + * Headtracker controller. + */ + public static final Type HEADTRACKER = new Type("Headtracker"); + + /** + * Rudder controller. + */ + public static final Type RUDDER = new Type("Rudder"); + + /** + * Stick controller, such as a joystick or flightstick. + */ + public static final Type STICK = new Type("Stick"); + + /** + * A trackball controller; note that this may sometimes be treated as a + * type of mouse. + */ + public static final Type TRACKBALL = new Type("Trackball"); + + /** + * A trackpad, such as a tablet, touchpad, or glidepad; + * note that this may sometimes be treated as a type of mouse. + */ + public static final Type TRACKPAD = new Type("Trackpad"); + + /** + * A wheel controller, such as a steering wheel (note + * that a mouse wheel is considered part of a mouse, not a + * wheel controller). + */ + public static final Type WHEEL = new Type("Wheel"); + } // class Controller.Type + + /** + * Common controller port types. + */ + public static final class PortType { + + /** + * Name of port type + */ + private final String name; + + /** + * Protected constructor + */ + protected PortType(String name) { + this.name = name; + } + + /** + * Returns a non-localized string description of this port type. + */ + public String toString() { + return name; + } + + /** + * Unknown port type + */ + public static final PortType UNKNOWN = new PortType("Unknown"); + + /** + * USB port + */ + public static final PortType USB = new PortType("USB port"); + + /** + * Standard game port + */ + public static final PortType GAME = new PortType("Game port"); + + /** + * Network port + */ + public static final PortType NETWORK = new PortType("Network port"); + + /** + * Serial port + */ + public static final PortType SERIAL = new PortType("Serial port"); + + /** + * i8042 + */ + public static final PortType I8042 = new PortType("i8042 (PS/2)"); + + /** + * Parallel port + */ + public static final PortType PARALLEL = new PortType("Parallel port"); + + } // class Controller.PortType +} // interface Controller diff --git a/src/core/net/java/games/input/ControllerEnvironment.java b/src/core/net/java/games/input/ControllerEnvironment.java new file mode 100644 index 0000000..6aa9ad8 --- /dev/null +++ b/src/core/net/java/games/input/ControllerEnvironment.java @@ -0,0 +1,161 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.File; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A ControllerEnvironment represents a collection of controllers that are + * physically or logically linked. By default, this corresponds to the + * environment for the local machine. + *

+ * In this reference implementation, this class can also be used to register + * controllers with the default environment as "plug-ins". A plug-in is + * created by subclassing ControllerEnvironment with a class that has a public + * no-argument constructor, implements the net.java.games.util.plugins.Plugin + * interface and has a name ending in "Plugin". + * (See net.java.games.input.DirectInputEnvironmentPlugin in the DXplugin + * part of the source tree for an example.) + * + * When the DefaultControllerEnvrionment is instanced it uses the plugin library + * to look for Plugins in both [java.home]/lib/controller and + * [user.dir]/controller. This allows controller plugins to be installed either + * globally for the entire Java environment or locally for just one particular + * Java app. + * + * For more information on the organization of plugins within the controller + * root directories, see net.java.games.util.plugins.Plugins (Note the + * plural -- "Plugins" not "Plugin" which is just a marker interface.) + * + */ +public abstract class ControllerEnvironment { + + static void log(String msg) { + Logger.getLogger(ControllerEnvironment.class.getName()).info(msg); + } + + /** + * The default controller environment + */ + private static ControllerEnvironment defaultEnvironment = + new DefaultControllerEnvironment(); + + /** + * List of controller listeners + */ + protected final ArrayList controllerListeners = new ArrayList<>(); + + /** + * Protected constructor for subclassing. + */ + protected ControllerEnvironment() { + if(System.getProperty("jinput.loglevel") != null) { + String loggerName = ControllerEnvironment.class.getPackage().getName(); + Level level = Level.parse(System.getProperty("jinput.loglevel")); + Logger.getLogger(loggerName).setLevel(level); + } + } + + /** + * Returns a list of all controllers available to this environment, + * or an empty array if there are no controllers in this environment. + */ + public abstract Controller[] getControllers(); + + /** + * Adds a listener for controller state change events. + */ + public void addControllerListener(ControllerListener l) { + assert l != null; + controllerListeners.add(l); + } + + /** + * Returns the isSupported status of this environment. + * What makes an environment supported or not is up to the + * particular plugin, but may include OS or available hardware. + */ + public abstract boolean isSupported(); + + /** + * Removes a listener for controller state change events. + */ + public void removeControllerListener(ControllerListener l) { + assert l != null; + controllerListeners.remove(l); + } + + /** + * Creates and sends an event to the controller listeners that a controller + * has been added. + */ + protected void fireControllerAdded(Controller c) { + ControllerEvent ev = new ControllerEvent(c); + Iterator it = controllerListeners.iterator(); + while (it.hasNext()) { + it.next().controllerAdded(ev); + } + } + + /** + * Creates and sends an event to the controller listeners that a controller + * has been lost. + */ + protected void fireControllerRemoved(Controller c) { + ControllerEvent ev = new ControllerEvent(c); + Iterator it = controllerListeners.iterator(); + while (it.hasNext()) { + it.next().controllerRemoved(ev); + } + } + + /** + * Returns the default environment for input controllers. + * This usually corresponds to the environment for the local machine. + */ + public static ControllerEnvironment getDefaultEnvironment() { + return defaultEnvironment; + } +} \ No newline at end of file diff --git a/src/core/net/java/games/input/ControllerEvent.java b/src/core/net/java/games/input/ControllerEvent.java new file mode 100644 index 0000000..14fbbcd --- /dev/null +++ b/src/core/net/java/games/input/ControllerEvent.java @@ -0,0 +1,61 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** + * An event that is fired when the state of a controller changes + */ +public class ControllerEvent { + + private Controller controller; + + /** + * Creates a controller event object. + */ + public ControllerEvent(Controller c) { + controller = c; + } + + /** + * Returns the controller for this event. + */ + public Controller getController() { + return controller; + } +} // class ControllerEvent diff --git a/src/core/net/java/games/input/ControllerListener.java b/src/core/net/java/games/input/ControllerListener.java new file mode 100644 index 0000000..8c58eff --- /dev/null +++ b/src/core/net/java/games/input/ControllerListener.java @@ -0,0 +1,55 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** + * A listener for changes in the state of controllers + */ +public interface ControllerListener { + + /** + * Invoked when a controller is lost. + */ + public abstract void controllerRemoved(ControllerEvent ev); + + /** + * Invoked when a controller has been added. + */ + public abstract void controllerAdded(ControllerEvent ev); +} // interface ControllerListener diff --git a/src/core/net/java/games/input/DefaultControllerEnvironment.java b/src/core/net/java/games/input/DefaultControllerEnvironment.java new file mode 100644 index 0000000..e16d6a8 --- /dev/null +++ b/src/core/net/java/games/input/DefaultControllerEnvironment.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +import net.java.games.util.plugins.Plugins; + +import java.io.File; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.StringTokenizer; +import java.util.logging.Logger; + +/** + * The default controller environment. + * + * @version %I% %G% + * @author Michael Martak + */ +class DefaultControllerEnvironment extends ControllerEnvironment { + static String libPath; + private static Logger log = Logger.getLogger(DefaultControllerEnvironment.class.getName()); + + /** + * Static utility method for loading native libraries. + * It will try to load from either the path given by + * the net.java.games.input.librarypath property + * or through System.loadLibrary(). + * + */ + static void loadLibrary(final String lib_name) { + AccessController.doPrivileged((PrivilegedAction) () -> { + String lib_path = System.getProperty("net.java.games.input.librarypath"); + if (lib_path != null) { + System.load(lib_path + File.separator + System.mapLibraryName(lib_name)); + } else { + System.loadLibrary(lib_name); + } + return null; + }); + } + + static String getPrivilegedProperty(final String property) { + return AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(property)); + } + + static String getPrivilegedProperty(final String property, final String default_value) { + return AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(property, default_value)); + } + + /** + * List of all controllers in this environment + */ + private ArrayList controllers; + + private Collection loadedPluginNames = new ArrayList<>(); + + /** + * Public no-arg constructor. + */ + public DefaultControllerEnvironment() { + } + + /** + * Returns a list of all controllers available to this environment, + * or an empty array if there are no controllers in this environment. + */ + @Override + public Controller[] getControllers() { + if (controllers == null) { + // Controller list has not been scanned. + controllers = new ArrayList<>(); + AccessController.doPrivileged((PrivilegedAction) () -> scanControllers()); + //Check the properties for specified controller classes + String pluginClasses = getPrivilegedProperty("jinput.plugins", "") + " " + getPrivilegedProperty("net.java.games.input.plugins", ""); + if (!getPrivilegedProperty("jinput.useDefaultPlugin", "true").toLowerCase().trim().equals("false") && !getPrivilegedProperty("net.java.games.input.useDefaultPlugin", "true").toLowerCase().trim().equals("false")) { + String osName = getPrivilegedProperty("os.name", "").trim(); + if (osName.equals("Linux")) { + pluginClasses = pluginClasses + " net.java.games.input.LinuxEnvironmentPlugin"; + } else if (osName.equals("Mac OS X")) { + pluginClasses = pluginClasses + " net.java.games.input.OSXEnvironmentPlugin"; + } else if (osName.equals("Windows XP") || osName.equals("Windows Vista") || osName.equals("Windows 7") || osName.equals("Windows 8") || osName.equals("Windows 8.1") || osName.equals("Windows 10")) { + pluginClasses = pluginClasses + " net.java.games.input.DirectAndRawInputEnvironmentPlugin"; + } else if (osName.equals("Windows 98") || osName.equals("Windows 2000")) { + pluginClasses = pluginClasses + " net.java.games.input.DirectInputEnvironmentPlugin"; + } else if (osName.startsWith("Windows")) { + log.warning("Found unknown Windows version: " + osName); + log.warning("Attempting to use default windows plug-in."); + pluginClasses = pluginClasses + " net.java.games.input.DirectAndRawInputEnvironmentPlugin"; + } else { + log.warning("Trying to use default plugin, OS name " + osName + " not recognised"); + } + } + + StringTokenizer pluginClassTok = new StringTokenizer(pluginClasses, " \t\n\r\f,;:"); + while (pluginClassTok.hasMoreTokens()) { + String className = pluginClassTok.nextToken(); + try { + if (!loadedPluginNames.contains(className)) { + log.fine("Loading: " + className); + Class ceClass = Class.forName(className); + ControllerEnvironment ce = (ControllerEnvironment) ceClass.getDeclaredConstructor().newInstance(); + if (ce.isSupported()) { + addControllers(ce.getControllers()); + loadedPluginNames.add(ce.getClass().getName()); + } else { + log(ceClass.getName() + " is not supported"); + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + Controller[] ret = new Controller[controllers.size()]; + Iterator it = controllers.iterator(); + int i = 0; + while (it.hasNext()) { + ret[i] = it.next(); + i++; + } + return ret; + } + + /* This is jeff's new plugin code using Jeff's Plugin manager */ + private Void scanControllers() { + String pluginPathName = getPrivilegedProperty("jinput.controllerPluginPath"); + if (pluginPathName == null) { + pluginPathName = "controller"; + } + + scanControllersAt(getPrivilegedProperty("java.home") + + File.separator + "lib" + File.separator + pluginPathName); + scanControllersAt(getPrivilegedProperty("user.dir") + + File.separator + pluginPathName); + + return null; + } + + private void scanControllersAt(String path) { + File file = new File(path); + if (!file.exists()) { + return; + } + try { + Plugins plugins = new Plugins(file); + @SuppressWarnings("unchecked") + Class[] envClasses = plugins.getExtends(ControllerEnvironment.class); + for (int i = 0; i < envClasses.length; i++) { + try { + ControllerEnvironment.log("ControllerEnvironment " + + envClasses[i].getName() + + " loaded by " + envClasses[i].getClassLoader()); + ControllerEnvironment ce = envClasses[i].getDeclaredConstructor().newInstance(); + if (ce.isSupported()) { + addControllers(ce.getControllers()); + loadedPluginNames.add(ce.getClass().getName()); + } else { + log(envClasses[i].getName() + " is not supported"); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Add the array of controllers to our list of controllers. + */ + private void addControllers(Controller[] c) { + for (int i = 0; i < c.length; i++) { + controllers.add(c[i]); + } + } + + public boolean isSupported() { + return true; + } +} diff --git a/src/core/net/java/games/input/Event.java b/src/core/net/java/games/input/Event.java new file mode 100644 index 0000000..c6bc5e9 --- /dev/null +++ b/src/core/net/java/games/input/Event.java @@ -0,0 +1,76 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +public final class Event { + private Component component; + private float value; + private long nanos; + + public final void set(Event other) { + this.set(other.getComponent(), other.getValue(), other.getNanos()); + } + + public final void set(Component component, float value, long nanos) { + this.component = component; + this.value = value; + this.nanos = nanos; + } + + public final Component getComponent() { + return component; + } + + public final float getValue() { + return value; + } + + /** + * Return the time the event happened, in nanoseconds. + * The time is relative and therefore can only be used + * to compare with other event times. + */ + public final long getNanos() { + return nanos; + } + + public final String toString() { + return "Event: component = " + component + " | value = " + value; + } +} diff --git a/src/core/net/java/games/input/EventQueue.java b/src/core/net/java/games/input/EventQueue.java new file mode 100644 index 0000000..a025d9c --- /dev/null +++ b/src/core/net/java/games/input/EventQueue.java @@ -0,0 +1,95 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** + * A FIFO queue for input events. + */ +public final class EventQueue { + private final Event[] queue; + + private int head; + private int tail; + + /** + * This is an internal method and should not be called by applications using the API + */ + public EventQueue(int size) { + queue = new Event[size + 1]; + for (int i = 0; i < queue.length; i++) + queue[i] = new Event(); + } + + /** + * This is an internal method and should not be called by applications using the API + */ + final synchronized void add(Event event) { + queue[tail].set(event); + tail = increase(tail); + } + + /** + * Check if the queue is full + * @return true if the queue is full + */ + final synchronized boolean isFull() { + return increase(tail) == head; + } + + /** + * This is an internal method and should not be called by applications using the API + */ + private final int increase(int x) { + return (x + 1)%queue.length; + } + + /** + * Populates the provided event with the details of the event on the head of the queue. + * + * @param event The event to populate + * @return false if there were no events left on the queue, otherwise true. + */ + public final synchronized boolean getNextEvent(Event event) { + if (head == tail) + return false; + event.set(queue[head]); + head = increase(head); + return true; + } +} diff --git a/src/core/net/java/games/input/Keyboard.java b/src/core/net/java/games/input/Keyboard.java new file mode 100644 index 0000000..e168869 --- /dev/null +++ b/src/core/net/java/games/input/Keyboard.java @@ -0,0 +1,69 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** + * A Keyboard is a type of controller consisting of a single controller, + * they keypad, which contains several axes (the keys). By default, all keys + * are set to receive polling data. + */ +public abstract class Keyboard extends AbstractController { + /** + * Protected constructor. + * Subclasses should initialize the array of axes to an array of keys. + * @param name The name of the keyboard + */ + protected Keyboard(String name, Component[] keys, Controller[] children, Rumbler[] rumblers) { + super(name, keys, children, rumblers); + } + + /** + * Returns the type of the Controller. + */ + public Type getType() { + return Type.KEYBOARD; + } + + public final boolean isKeyDown(Component.Identifier.Key key_id) { + Component key = getComponent(key_id); + if (key == null) + return false; + return key.getPollData() != 0; + } +} // class Keyboard diff --git a/src/core/net/java/games/input/Mouse.java b/src/core/net/java/games/input/Mouse.java new file mode 100644 index 0000000..27abad5 --- /dev/null +++ b/src/core/net/java/games/input/Mouse.java @@ -0,0 +1,183 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/** *************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + **************************************************************************** */ +package net.java.games.input; + +/** + * A Mouse is a type of controller consisting of two child controllers, + * a ball and a button pad. This includes devices such as touch pads, + * trackballs, and fingersticks. + */ +public abstract class Mouse extends AbstractController { + protected Mouse(String name, Component[] components, Controller[] children, Rumbler[] rumblers) { + super(name, components, children, rumblers); + } + + /** + * Returns the type of the Controller. + */ + public Type getType() { + return Type.MOUSE; + } + + /** + * Returns the x-axis for the mouse ball, never null. + */ + public Component getX() { + return getComponent(Component.Identifier.Axis.X); + } + + /** + * Returns the y-axis for the mouse ball, never null. + */ + public Component getY() { + return getComponent(Component.Identifier.Axis.Y); + } + + /** + * Returns the mouse wheel, or null if no mouse wheel is present. + */ + public Component getWheel() { + return getComponent(Component.Identifier.Axis.Z); + } + + /** + * Returns the left or primary mouse button, never null. + */ + public Component getPrimaryButton() { + Component primaryButton = getComponent(Component.Identifier.Button.LEFT); + if (primaryButton == null) { + primaryButton = getComponent(Component.Identifier.Button._1); + } + return primaryButton; + } + + /** + * Returns the right or secondary mouse button, null if the mouse is + * a single-button mouse. + */ + public Component getSecondaryButton() { + Component secondaryButton = getComponent(Component.Identifier.Button.RIGHT); + if (secondaryButton == null) { + secondaryButton = getComponent(Component.Identifier.Button._2); + } + return secondaryButton; + } + + /** + * Returns the middle or tertiary mouse button, null if the mouse has + * fewer than three buttons. + */ + public Component getTertiaryButton() { + Component tertiaryButton = getComponent(Component.Identifier.Button.MIDDLE); + if (tertiaryButton == null) { + tertiaryButton = getComponent(Component.Identifier.Button._3); + } + return tertiaryButton; + } + + /** + * Returns the left mouse button. + */ + public Component getLeft() { + return getComponent(Component.Identifier.Button.LEFT); + } + + /** + * Returns the right, null if the mouse is a single-button mouse. + */ + public Component getRight() { + return getComponent(Component.Identifier.Button.RIGHT); + } + + /** + * Returns the middle, null if the mouse has fewer than three buttons. + */ + public Component getMiddle() { + return getComponent(Component.Identifier.Button.MIDDLE); + } + + /** + * Returns the side or 4th mouse button, null if the mouse has + * fewer than 4 buttons. + */ + public Component getSide() { + return getComponent(Component.Identifier.Button.SIDE); + } + + /** + * Returns the extra or 5th mouse button, null if the mouse has + * fewer than 5 buttons. + */ + public Component getExtra() { + return getComponent(Component.Identifier.Button.EXTRA); + } + + /** + * Returns the forward mouse button, null if the mouse hasn't + * got one. + */ + public Component getForward() { + return getComponent(Component.Identifier.Button.FORWARD); + } + + /** + * Returns the back mouse button, null if the mouse hasn't + * got one. + */ + public Component getBack() { + return getComponent(Component.Identifier.Button.BACK); + } + + /** + * Returns forth mouse button, null if the mouse hasn't + * got one. + */ + public Component getButton3() { + return getComponent(Component.Identifier.Button._3); + } + + /** + * Returns fifth mouse button, null if the mouse hasn't + * got one. + */ + public Component getButton4() { + return getComponent(Component.Identifier.Button._4); + } + +} // class Mouse diff --git a/src/core/net/java/games/input/PluginClassLoader.java b/src/core/net/java/games/input/PluginClassLoader.java new file mode 100644 index 0000000..da5eb62 --- /dev/null +++ b/src/core/net/java/games/input/PluginClassLoader.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.StringTokenizer; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Loads all plugins. + * + * @version %I% %G% + * @author Michael Martak + */ +class PluginClassLoader extends ClassLoader { + + /** + * Location of directory to look for plugins + */ + private static String pluginDirectory; + + /** + * File filter for JAR files + */ + private static final FileFilter JAR_FILTER = new JarFileFilter(); + + /** + * Create a new class loader for loading plugins + */ + public PluginClassLoader() { + super(Thread.currentThread().getContextClassLoader()); + } + + /** + * Overrides findClass to first look in the parent class loader, + * then try loading the class from the plugin file system. + */ + protected Class findClass(String name) + throws ClassNotFoundException { + // Try loading the class from the file system. + byte[] b = loadClassData(name); + return defineClass(name, b, 0, b.length); + } + + /** + * Load the class data from the file system + */ + private byte[] loadClassData(String name) + throws ClassNotFoundException { + if (pluginDirectory == null) { + pluginDirectory = DefaultControllerEnvironment.libPath + + File.separator + "controller"; + } + try { + return loadClassFromDirectory(name); + } catch (Exception e) { + try { + return loadClassFromJAR(name); + } catch (IOException e2) { + throw new ClassNotFoundException(name, e2); + } + } + } + + /** + * Load the class data from the file system based on parsing + * the class name (packages). + */ + private byte[] loadClassFromDirectory(String name) + throws ClassNotFoundException, IOException { + // Parse the class name into package directories. + // Look for the class in the plugin directory. + StringTokenizer tokenizer = new StringTokenizer(name, "."); + StringBuffer path = new StringBuffer(pluginDirectory); + while (tokenizer.hasMoreTokens()) { + path.append(File.separator); + path.append(tokenizer.nextToken()); + } + path.append(".class"); + File file = new File(path.toString()); + if (!file.exists()) { + throw new ClassNotFoundException(name); + } + try(FileInputStream fileInputStream = new FileInputStream(file)) { + assert file.length() <= Integer.MAX_VALUE; + int length = (int) file.length(); + byte[] bytes = new byte[length]; + int length2 = fileInputStream.read(bytes); + assert length == length2; + return bytes; + } + } + + /** + * Scans through the plugin directory for JAR files and + * attempts to load the class data from each JAR file. + */ + private byte[] loadClassFromJAR(String name) + throws ClassNotFoundException, IOException { + File dir = new File(pluginDirectory); + File[] jarFiles = dir.listFiles(JAR_FILTER); + if (jarFiles == null) { + throw new ClassNotFoundException("Could not find class " + name); + } + for (int i = 0; i < jarFiles.length; i++) { + JarFile jarfile = new JarFile(jarFiles[i]); + JarEntry jarentry = jarfile.getJarEntry(name + ".class"); + if (jarentry != null) { + try(InputStream jarInputStream = jarfile.getInputStream(jarentry)) { + assert jarentry.getSize() <= Integer.MAX_VALUE; + int length = (int) jarentry.getSize(); + assert length >= 0; + byte[] bytes = new byte[length]; + int length2 = jarInputStream.read(bytes); + assert length == length2; + return bytes; + } + } + } + throw new FileNotFoundException(name); + } + + /** + * Filters out all non-JAR files, based on whether or not they + * end in ".JAR" (case-insensitive). + */ + private static class JarFileFilter implements FileFilter { + public boolean accept(File file) { + return file.getName().toUpperCase().endsWith(".JAR"); + } + } +} + diff --git a/src/core/net/java/games/input/Rumbler.java b/src/core/net/java/games/input/Rumbler.java new file mode 100644 index 0000000..9d64cf3 --- /dev/null +++ b/src/core/net/java/games/input/Rumbler.java @@ -0,0 +1,67 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** + * A Rumbler is a controller's mechanism for delivering feedback + * to the user through the device. + */ +public interface Rumbler { + + /** + * Rumble with the specified intensity. + */ + public abstract void rumble(float intensity); + + /** + * Get the string name of the axis the rumbler is attached to + * + * @return The axis name + */ + public String getAxisName(); + + /** + * Get the axis identifier the rumbler is attached to + * + * @return The axis identifier + */ + public Component.Identifier getAxisIdentifier(); + + +} // interface Rumbler diff --git a/src/core/net/java/games/input/Version.java b/src/core/net/java/games/input/Version.java new file mode 100644 index 0000000..89092d2 --- /dev/null +++ b/src/core/net/java/games/input/Version.java @@ -0,0 +1,101 @@ +/* +* Copyright (c) 2004 Sun Microsystems, Inc. All Rights Reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* -Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* -Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* Neither the name of Sun Microsystems, Inc. or the names of contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS +* LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A +* RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. +* IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT +* OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR +* PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, +* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS +* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for use in the +* design, construction, operation or maintenance of any nuclear facility. + */ +package net.java.games.input; + +import java.io.IOException; +import java.net.URL; +import java.util.PropertyResourceBundle; + +/** + * The version and build number of this implementation. + * Version numbers for a release are of the form: w.x.y, where: + *

    + *
  • + * w - the major version number of the release. This number should + * start at 1. Typically, a bump in the major version number + * signifies that the release breaks backwards compatibility + * with some older release. + *
  • + *
  • + * x - minor version number. This number starts at 0. A bump in + * the minor version number signifies a release that has significant + * new functionality. + *
  • + *
  • + * y - minor-minor version number number. This number starts at 0. A + * bump in the minor-minor version number signifies that new bug + * fixes have been added to the build. + *
  • + *
+ * + * For example, the following are all valid version strings: + *
    + *
  • 1.1.2
  • + *
  • 1.3.5-SNAPSHOT
  • + *
  • 4.7.1-M2
  • + *
+ * + * @version 2.10 + */ +public final class Version { + + /** + * Private constructor - no need for user to create + * an instance of this class. + */ + private Version() { + } + + public static void main(String[] args) { + String version = getVersion(); + System.out.println("jinput version " + version); + } + + /** + * Returns the verison string and build number of + * this implementation. See the class descritpion + * for the version string format. + * + * @return The version string of this implementation. + */ + public static String getVersion() { + URL url = Version.class.getResource("jinput.properties"); + try { + PropertyResourceBundle prb = new PropertyResourceBundle(url.openStream()); + String version = prb.getString("version"); + return version; + } catch (IOException ex) { + return "Unversioned"; + } + } +} diff --git a/src/core/net/java/games/input/jinput.properties b/src/core/net/java/games/input/jinput.properties new file mode 100644 index 0000000..2b0e575 --- /dev/null +++ b/src/core/net/java/games/input/jinput.properties @@ -0,0 +1,2 @@ +version=2.10 +date=13/05/2021 diff --git a/src/core/net/java/games/input/package.html b/src/core/net/java/games/input/package.html new file mode 100644 index 0000000..58c075f --- /dev/null +++ b/src/core/net/java/games/input/package.html @@ -0,0 +1,14 @@ + + + + +Top level package for JInput. + +

Package Specification

+ +Use -Djinput.useDefaultPlugin=false (or net.java.games.input.useDefaultPlugin=false) to disable automatic loading of the default plugin for the platform.
+Use -Djinput.plugins (or net.java.games.input.plugins) and specifiy a list of class name to over ride the plugins system. This will force the classes passed to be loaded first, then plugins will be searched for in the default manner (./controller/*.jar)
+Use -Djinput.controllerPluginPath to change the path the plugins mechanism will use to search for plugin Jars. +Use -Djinput.loglevel to change the logging level using Java util logging level strings. + + \ No newline at end of file diff --git a/src/jutils/net/java/games/util/Version.java b/src/jutils/net/java/games/util/Version.java new file mode 100644 index 0000000..62bd4a1 --- /dev/null +++ b/src/jutils/net/java/games/util/Version.java @@ -0,0 +1,104 @@ +/* +* Copyright (c) 2004 Sun Microsystems, Inc. All Rights Reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* -Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* -Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* Neither the name of Sun Microsystems, Inc. or the names of contributors may +* be used to endorse or promote products derived from this software without +* specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS +* LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A +* RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. +* IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT +* OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR +* PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, +* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS +* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for use in the +* design, construction, operation or maintenance of any nuclear facility. +*/ + +package net.java.games.util; + +/** + * The version and build number of this implementation. + * Version numbers for a release are of the form: w.x.y[-a]-z, where: + *
    + *
  • + * w - the major version number of the release. This number should + * start at 1. Typically, a bump in the major version number + * signifies that the release breaks backwards compatibility + * with some older release. + *
  • + *
  • + * x - minor version number. This number starts at 0. A bump in + * the minor version number signifies a release that has significant + * new functionality. + *
  • + *
  • + * y - minor-minor version number number. This number starts at 0. A + * bump in the minor-minor version number signifies that new bug + * fixes have been added to the build. + *
  • + *
  • + * a - an optional build designator followed by a digit. Valid build + * designators are: + *
      + *
    • alpha
    • + *
    • beta
    • + *
    + *
  • + *
  • + * z - build number. This is used to specify the build number of the + * release. This is usually only important to people that use + * the daily build of a project. The format is the lower-case + * letter 'b' followed by a two digit number. + *
  • + *
+ * + * For example, the following are all valid version strings: + *
    + *
  • 1.1.2-b02
  • + *
  • 1.3.5-alpha1-b19
  • + *
  • 4.7.1-beta3-b20
  • + *
+ * + */ +public final class Version { + + /** + * Private constructor - no need for user to create + * an instance of this class. + */ + private Version() { + } + + /** + * Version string of this build. + */ + private static final String version = "1.0.0-b01"; + + /** + * Returns the verison string and build number of + * this implementation. See the class descritpion + * for the version string format. + * + * @return The version string of this implementation. + */ + public static String getVersion() { + return version; + } +} diff --git a/src/jutils/net/java/games/util/package.html b/src/jutils/net/java/games/util/package.html new file mode 100644 index 0000000..62832a4 --- /dev/null +++ b/src/jutils/net/java/games/util/package.html @@ -0,0 +1,7 @@ + + + + +Utility package for JInput. See https://github.com/jinput/jutils. + + \ No newline at end of file diff --git a/src/jutils/net/java/games/util/plugins/Plugin.java b/src/jutils/net/java/games/util/plugins/Plugin.java new file mode 100644 index 0000000..cfb533b --- /dev/null +++ b/src/jutils/net/java/games/util/plugins/Plugin.java @@ -0,0 +1,60 @@ +/* + * Plugin.java + * + * Created on April 18, 2003, 11:29 AM + */ + +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ + +package net.java.games.util.plugins; + +/** This is a marker interface used to mark plugins in a Jar file + * for retrieval by the Plugins class. In order for a class to be + * treated as a Plugin the following must be true: + * + * (1) The name of the class must end with "Plugin". + * (ie MedianCutFilterPlugin, DirectInput EnvrionmentPlugin) + * + * (2) The class must implement the Plugin interface. It can do + * so directly, through inheritence of either a superclass + * that implements this interface, or through the implementation + * of an interface that extends this interface. + * + * + * @author Jeffrey P. Kesselman + * + */ +public interface Plugin { + +} diff --git a/src/jutils/net/java/games/util/plugins/PluginLoader.java b/src/jutils/net/java/games/util/plugins/PluginLoader.java new file mode 100644 index 0000000..00545eb --- /dev/null +++ b/src/jutils/net/java/games/util/plugins/PluginLoader.java @@ -0,0 +1,169 @@ +/* + * PluginLodaer.java + * + * Created on April 18, 2003, 11:32 AM + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.util.plugins; + +/** + * + * @author jeff + */ +import java.io.*; +import java.net.*; + +/** This class is used internally by the Plugin system. + * End users of the system are unlikely to need to be aware + * of it. + * + * + * This is the class loader used to keep the namespaces of + * different plugins isolated from each other and from the + * main app code. One plugin loader is created per Jar + * file in the sub-directory tree of the plugin directory. + * + * In addition to isolating java classes this loader also isolates + * DLLs such that plugins with conflicting DLL names may be + * used by simply placing the plugin and its associated DLL + * in a sub-folder of its own. + * + * This class also currently implements methods for testing + * classes for inheritance of superclasses or interfaces. + * This code is genericly useful and should really be moved + * to a seperate ClassUtils class. + * @author Jeffrey Kesselman + */ +public class PluginLoader extends URLClassLoader { + static final boolean DEBUG = false; + File parentDir; + boolean localDLLs = true; + /** Creates a new instance of PluginLodaer + * If the system property "net.java.games.util.plugins.nolocalnative" is + * not set then the laoder will look for requried native libs in the + * same directory as the plugin jar. (Useful for handling name + * collision between plugins). If it IS set however, then it will + * fall back to the default way of loading natives. (Necessary for + * Java Web Start.) + * @param jf The JarFile to load the Plugins from. + * @throws MalformedURLException Will throw this exception if jf does not refer to a + * legitimate Jar file. + */ + public PluginLoader(File jf) throws MalformedURLException { + super(new URL[] {jf.toURL()}, + Thread.currentThread().getContextClassLoader()); + parentDir = jf.getParentFile(); + if (System.getProperty("net.java.games.util.plugins.nolocalnative") + !=null){ + localDLLs = false; + } + } + + /** This method is queried by the System.loadLibrary() + * code to find the actual native name and path to the + * native library to load. + * + * This subclass implementation of this method ensures that + * the native library will be loaded from, and only from, + * the parent directory of the Jar file this loader was + * created to support. This allows different Plugins + * with supporting DLLs of the same name to co-exist, each + * in their own subdirectory. + * + * Setting the global "localDLLs" by setting the property + * net.java.games.util.plugins.nolocalnative defeats this behavior. + * This is necessary for Java Web Start apps which have strong + * restrictions on where and how native libs can be loaded. + * + * @param libname The JNI name of the native library to locate. + * @return Returns a string describing the actual loation of the + * native library in the native file system. + */ + protected String findLibrary(String libname){ + if (localDLLs) { + String libpath = parentDir.getPath() + File.separator + + System.mapLibraryName(libname); + if (DEBUG) { + System.out.println("Returning libname of: " + libpath); + } + return libpath; + } else { + return super.findLibrary(libname); + } + } + + /** This function is called as part of scanning the Jar for + * plugins. It checks to make sure the class passed in is + * a legitimate plugin, which is to say that it meets + * the following criteria: + * + * (1) Is not itself an interface + * (2) Implements the Plugin marker interface either directly + * or through inheritance. + * + * interface, either + * @param pc The potential plug-in class to vette. + * @return Returns true if the class meets the criteria for a + * plugin. Otherwise returns false. + */ + public boolean attemptPluginDefine(Class pc){ + return ((!pc.isInterface()) && classImplementsPlugin(pc)); + } + + private boolean classImplementsPlugin(Class testClass){ + if (testClass == null) return false; // end of tree + if (DEBUG) { + System.out.println("testing class "+testClass.getName()); + } + Class[] implementedInterfaces = testClass.getInterfaces(); + for(int i=0;iany of the passed in set of + * Interfaces (either directly or through inheritance.) + * @param interfaces A set of interfaces to match against the interfaces + * implemented by the plugin classes. + * @return The list of plugin classes that implement at least + * one member of the passed in set of interfaces. + */ + public Class[] getImplementsAny(Class[] interfaces){ + List matchList = new ArrayList(pluginList.size()); + Set interfaceSet = new HashSet(); + for(int i=0;iall of the passed in set of + * Interfaces (either directly or through inheritance.) + * @param interfaces A set of interfaces to match against the interfaces + * implemented by the plugin classes. + * @return The list of plugin classes that implement at least + * one member of the passed in set of interfaces. + */ + public Class[] getImplementsAll(Class[] interfaces){ + List matchList = new ArrayList(pluginList.size()); + Set interfaceSet = new HashSet(); + for(int i=0;i + + + +Utility package for JInput Plugins. + + \ No newline at end of file diff --git a/src/manifest.mf b/src/manifest.mf new file mode 100644 index 0000000..2552295 --- /dev/null +++ b/src/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: net.java.games.input.Version +Automatic-Module-Name: net.java.games.jinput diff --git a/src/native/common/util.c b/src/native/common/util.c new file mode 100644 index 0000000..089ba17 --- /dev/null +++ b/src/native/common/util.c @@ -0,0 +1,118 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ + +#include +#include +#include +#include "util.h" + +static jstring sprintfJavaString(JNIEnv *env, const char *format, va_list ap) { +#define BUFFER_SIZE 4000 + char buffer[BUFFER_SIZE]; + jstring str; +#ifdef _MSC_VER + vsnprintf_s(buffer, BUFFER_SIZE, _TRUNCATE, format, ap); +#else + vsnprintf(buffer, BUFFER_SIZE, format, ap); +#endif + buffer[BUFFER_SIZE - 1] = '\0'; + str = (*env)->NewStringUTF(env, buffer); + return str; +} + +void printfJava(JNIEnv *env, const char *format, ...) { + jstring str; + jclass org_lwjgl_LWJGLUtil_class; + jmethodID log_method; + va_list ap; + va_start(ap, format); + str = sprintfJavaString(env, format, ap); + va_end(ap); + org_lwjgl_LWJGLUtil_class = (*env)->FindClass(env, "net/java/games/input/ControllerEnvironment"); + if (org_lwjgl_LWJGLUtil_class == NULL) + return; + log_method = (*env)->GetStaticMethodID(env, org_lwjgl_LWJGLUtil_class, "log", "(Ljava/lang/String;)V"); + if (log_method == NULL) + return; + (*env)->CallStaticVoidMethod(env, org_lwjgl_LWJGLUtil_class, log_method, str); +} + +static void throwException(JNIEnv * env, const char *exception_name, const char *format, va_list ap) { + jstring str; + jobject exception; + + if ((*env)->ExceptionCheck(env) == JNI_TRUE) + return; // The JVM crashes if we try to throw two exceptions from one native call + str = sprintfJavaString(env, format, ap); + exception = newJObject(env, exception_name, "(Ljava/lang/String;)V", str); + (*env)->Throw(env, exception); +} + +void throwRuntimeException(JNIEnv * env, const char *format, ...) { + va_list ap; + va_start(ap, format); + throwException(env, "java/lang/RuntimeException", format, ap); + va_end(ap); +} + +void throwIOException(JNIEnv * env, const char *format, ...) { + va_list ap; + va_start(ap, format); + throwException(env, "java/io/IOException", format, ap); + va_end(ap); +} + +jobject newJObject(JNIEnv * env, const char *class_name, const char *constructor_signature, ...) { + va_list ap; + jclass clazz; + jmethodID constructor; + jobject obj; + + clazz = (*env)->FindClass(env, class_name); + if (clazz == NULL) + return NULL; + constructor = (*env)->GetMethodID(env, clazz, "", constructor_signature); + if (constructor == NULL) + return NULL; + va_start(ap, constructor_signature); + obj = (*env)->NewObjectV(env, clazz, constructor, ap); + va_end(ap); + return obj; +} + diff --git a/src/native/common/util.h b/src/native/common/util.h new file mode 100644 index 0000000..657a93d --- /dev/null +++ b/src/native/common/util.h @@ -0,0 +1,53 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ + +#include +#include +#include +#ifdef _MSC_VER +#include +#else +#include +#endif + +extern void printfJava(JNIEnv *env, const char *format, ...); +extern void throwRuntimeException(JNIEnv * env, const char *format, ...); +extern void throwIOException(JNIEnv * env, const char *format, ...); +extern jobject newJObject(JNIEnv * env, const char *class_name, const char *constructor_signature, ...); + diff --git a/src/native/linux/build.xml b/src/native/linux/build.xml new file mode 100644 index 0000000..ccdc209 --- /dev/null +++ b/src/native/linux/build.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/native/linux/getDefinitions b/src/native/linux/getDefinitions new file mode 100644 index 0000000..e5d127d --- /dev/null +++ b/src/native/linux/getDefinitions @@ -0,0 +1,60 @@ +#!/bin/gawk -f + +# Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. Redistributions in binary +# form must reproduce the above copyright notice, this list of conditions and +# the following disclaimer in the documentation and/or other materials provided +# with the distribution. +# The name of the author may not be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + +NR == 1 { + FILENAME == ARGV[1]; + printf("package net.java.games.input;\n\n") + printf("\n"); + printf("/**\n * This file is generated from %s please do not edit\n */\n", FILENAME); + printf("class NativeDefinitions {\n") +} +/#define ABS_/ { + printf(" public static final int %s = %s;\n", $2, $3) +} +/#define REL_/ { + printf(" public static final int %s = %s;\n", $2, $3) +} +/#define BTN_/ { + printf(" public static final int %s = %s;\n", $2, $3) +} +/#define KEY_/ { + printf(" public static final int %s = %s;\n", $2, $3) +} +/#define BUS_/ { + printf(" public static final int %s = %s;\n", $2, $3) +} +/#define EV_/ { + printf(" public static final int %s = %s;\n", $2, $3) +} +/#define FF_/ { + printf(" public static final int %s = %s;\n", $2, $3) +} +/#define USAGE_/ { + printf(" public static final int %s = %s;\n", $2, $3) +} +END { + printf("}\n"); +} diff --git a/src/native/linux/net_java_games_input_LinuxEventDevice.c b/src/native/linux/net_java_games_input_LinuxEventDevice.c new file mode 100644 index 0000000..18913c2 --- /dev/null +++ b/src/native/linux/net_java_games_input_LinuxEventDevice.c @@ -0,0 +1,243 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "net_java_games_input_LinuxEventDevice.h" + +JNIEXPORT jlong JNICALL Java_net_java_games_input_LinuxEventDevice_nOpen(JNIEnv *env, jclass unused, jstring path, jboolean rw_flag) { + const char *path_str = (*env)->GetStringUTFChars(env, path, NULL); + if (path_str == NULL) + return -1; + int flags = rw_flag == JNI_TRUE ? O_RDWR : O_RDONLY; + flags = flags | O_NONBLOCK; + int fd = open(path_str, flags); + if (fd == -1) + throwIOException(env, "Failed to open device %s (%d)\n", path_str, errno); + (*env)->ReleaseStringUTFChars(env, path, path_str); + return fd; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_LinuxEventDevice_nClose(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + int result = close(fd); + if (result == -1) + throwIOException(env, "Failed to close device (%d)\n", errno); +} + +JNIEXPORT jstring JNICALL Java_net_java_games_input_LinuxEventDevice_nGetName(JNIEnv *env, jclass unused, jlong fd_address) { +#define BUFFER_SIZE 1024 + int fd = (int)fd_address; + char device_name[BUFFER_SIZE]; + + if (ioctl(fd, EVIOCGNAME(BUFFER_SIZE), device_name) == -1) { + throwIOException(env, "Failed to get device name (%d)\n", errno); + return NULL; + } + jstring jstr = (*env)->NewStringUTF(env, device_name); + return jstr; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_LinuxEventDevice_nGetKeyStates(JNIEnv *env, jclass unused, jlong fd_address, jbyteArray bits_array) { + int fd = (int)fd_address; + jsize len = (*env)->GetArrayLength(env, bits_array); + jbyte *bits = (*env)->GetByteArrayElements(env, bits_array, NULL); + if (bits == NULL) + return; + int res = ioctl(fd, EVIOCGKEY(len), bits); + (*env)->ReleaseByteArrayElements(env, bits_array, bits, 0); + if (res == -1) + throwIOException(env, "Failed to get device key states (%d)\n", errno); +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_LinuxEventDevice_nGetVersion(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + int version; + if (ioctl(fd, EVIOCGVERSION, &version) == -1) { + throwIOException(env, "Failed to get device version (%d)\n", errno); + return -1; + } + return version; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_LinuxEventDevice_nGetNumEffects(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + int num_effects; + if (ioctl(fd, EVIOCGEFFECTS, &num_effects) == -1) { + throwIOException(env, "Failed to get number of device effects (%d)\n", errno); + return -1; + } + return num_effects; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_LinuxEventDevice_nGetBits(JNIEnv *env, jclass unused, jlong fd_address, jint evtype, jbyteArray bits_array) { + int fd = (int)fd_address; + jsize len = (*env)->GetArrayLength(env, bits_array); + jbyte *bits = (*env)->GetByteArrayElements(env, bits_array, NULL); + if (bits == NULL) + return; + int res = ioctl(fd, EVIOCGBIT(evtype, len), bits); + (*env)->ReleaseByteArrayElements(env, bits_array, bits, 0); + if (res == -1) + throwIOException(env, "Failed to get device bits (%d)\n", errno); +} + +JNIEXPORT jobject JNICALL Java_net_java_games_input_LinuxEventDevice_nGetInputID(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + jclass input_id_class = (*env)->FindClass(env, "net/java/games/input/LinuxInputID"); + if (input_id_class == NULL) + return NULL; + jmethodID input_id_constructor = (*env)->GetMethodID(env, input_id_class, "", "(IIII)V"); + if (input_id_constructor == NULL) + return NULL; + struct input_id id; + int result = ioctl(fd, EVIOCGID, &id); + if (result == -1) { + throwIOException(env, "Failed to get input id for device (%d)\n", errno); + return NULL; + } + return (*env)->NewObject(env, input_id_class, input_id_constructor, (jint)id.bustype, (jint)id.vendor, (jint)id.product, (jint)id.version); +} + +JNIEXPORT void JNICALL Java_net_java_games_input_LinuxEventDevice_nGetAbsInfo(JNIEnv *env, jclass unused, jlong fd_address, jint abs_axis, jobject abs_info_return) { + int fd = (int)fd_address; + jclass abs_info_class = (*env)->GetObjectClass(env, abs_info_return); + if (abs_info_class == NULL) + return; + jmethodID abs_info_set = (*env)->GetMethodID(env, abs_info_class, "set", "(IIIII)V"); + if (abs_info_set == NULL) + return; + struct input_absinfo abs_info; + int result = ioctl(fd, EVIOCGABS(abs_axis), &abs_info); + if (result == -1) { + throwIOException(env, "Failed to get abs info for axis (%d)\n", errno); + return; + } + (*env)->CallVoidMethod(env, abs_info_return, abs_info_set, (jint)abs_info.value, (jint)abs_info.minimum, (jint)abs_info.maximum, (jint)abs_info.fuzz, (jint)abs_info.flat); +} + +JNIEXPORT jboolean JNICALL Java_net_java_games_input_LinuxEventDevice_nGetNextEvent(JNIEnv *env, jclass unused, jlong fd_address, jobject linux_event_return) { + int fd = (int)fd_address; + jclass linux_event_class = (*env)->GetObjectClass(env, linux_event_return); + if (linux_event_class == NULL) + return JNI_FALSE; + jmethodID linux_event_set = (*env)->GetMethodID(env, linux_event_class, "set", "(JJIII)V"); + if (linux_event_set == NULL) + return JNI_FALSE; + struct input_event event; + if (read(fd, &event, sizeof(struct input_event)) == -1) { + if (errno == EAGAIN) + return JNI_FALSE; + throwIOException(env, "Failed to read next device event (%d)\n", errno); + return JNI_FALSE; + } + (*env)->CallVoidMethod(env, linux_event_return, linux_event_set, (jlong)event.time.tv_sec, (jlong)event.time.tv_usec, (jint)event.type, (jint)event.code, (jint)event.value); + return JNI_TRUE; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_LinuxEventDevice_nUploadRumbleEffect(JNIEnv *env, jclass unused, jlong fd_address, jint id, jint direction, jint trigger_button, jint trigger_interval, jint replay_length, jint replay_delay, jint strong_magnitude, jint weak_magnitude) { + int fd = (int)fd_address; + struct ff_effect effect; + + effect.type = FF_RUMBLE; + effect.id = id; + effect.trigger.button = trigger_button; + effect.trigger.interval = trigger_interval; + effect.replay.length = replay_length; + effect.replay.delay = replay_delay; + effect.direction = direction; + effect.u.rumble.strong_magnitude = strong_magnitude; + effect.u.rumble.weak_magnitude = weak_magnitude; + + if (ioctl(fd, EVIOCSFF, &effect) == -1) { + throwIOException(env, "Failed to upload effect (%d)\n", errno); + return -1; + } + return effect.id; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_LinuxEventDevice_nUploadConstantEffect(JNIEnv *env, jclass unused, jlong fd_address, jint id, jint direction, jint trigger_button, jint trigger_interval, jint replay_length, jint replay_delay, jint constant_level, jint constant_env_attack_length, jint constant_env_attack_level, jint constant_env_fade_length, jint constant_env_fade_level) { + int fd = (int)fd_address; + struct ff_effect effect; + + effect.type = FF_CONSTANT; + effect.id = id; + effect.trigger.button = trigger_button; + effect.trigger.interval = trigger_interval; + effect.replay.length = replay_length; + effect.replay.delay = replay_delay; + effect.direction = direction; + effect.u.constant.level = constant_level; + effect.u.constant.envelope.attack_length = constant_env_attack_length; + effect.u.constant.envelope.attack_level = constant_env_attack_level; + effect.u.constant.envelope.fade_length = constant_env_fade_length; + effect.u.constant.envelope.fade_level = constant_env_fade_level; + + if (ioctl(fd, EVIOCSFF, &effect) == -1) { + throwIOException(env, "Failed to upload effect (%d)\n", errno); + return -1; + } + return effect.id; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_LinuxEventDevice_nWriteEvent(JNIEnv *env, jclass unused, jlong fd_address, jint type, jint code, jint value) { + int fd = (int)fd_address; + struct input_event event; + event.type = type; + event.code = code; + event.value = value; + + if (write(fd, &event, sizeof(event)) == -1) { + throwIOException(env, "Failed to write to device (%d)\n", errno); + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_LinuxEventDevice_nEraseEffect(JNIEnv *env, jclass unused, jlong fd_address, jint ff_id) { + int fd = (int)fd_address; + int ff_id_int = ff_id; + + if (ioctl(fd, EVIOCRMFF, &ff_id_int) == -1) + throwIOException(env, "Failed to erase effect (%d)\n", errno); +} diff --git a/src/native/linux/net_java_games_input_LinuxJoystickDevice.c b/src/native/linux/net_java_games_input_LinuxJoystickDevice.c new file mode 100644 index 0000000..718bef3 --- /dev/null +++ b/src/native/linux/net_java_games_input_LinuxJoystickDevice.c @@ -0,0 +1,158 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "net_java_games_input_LinuxJoystickDevice.h" + +JNIEXPORT jlong JNICALL Java_net_java_games_input_LinuxJoystickDevice_nOpen(JNIEnv *env, jclass unused, jstring path) { + const char *path_str = (*env)->GetStringUTFChars(env, path, NULL); + if (path_str == NULL) + return -1; + int fd = open(path_str, O_RDONLY | O_NONBLOCK); + if (fd == -1) + throwIOException(env, "Failed to open device %s (%d)\n", path_str, errno); + (*env)->ReleaseStringUTFChars(env, path, path_str); + return fd; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_LinuxJoystickDevice_nClose(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + int result = close(fd); + if (result == -1) + throwIOException(env, "Failed to close device (%d)\n", errno); +} + +JNIEXPORT jstring JNICALL Java_net_java_games_input_LinuxJoystickDevice_nGetName(JNIEnv *env, jclass unused, jlong fd_address) { +#define BUFFER_SIZE 1024 + int fd = (int)fd_address; + char device_name[BUFFER_SIZE]; + + if (ioctl(fd, JSIOCGNAME(BUFFER_SIZE), device_name) == -1) { + throwIOException(env, "Failed to get device name (%d)\n", errno); + return NULL; + } + jstring jstr = (*env)->NewStringUTF(env, device_name); + return jstr; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_LinuxJoystickDevice_nGetVersion(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + __u32 version; + if (ioctl(fd, JSIOCGVERSION, &version) == -1) { + throwIOException(env, "Failed to get device version (%d)\n", errno); + return -1; + } + return version; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_LinuxJoystickDevice_nGetNumButtons(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + __u8 num_buttons; + if (ioctl(fd, JSIOCGBUTTONS, &num_buttons) == -1) { + throwIOException(env, "Failed to get number of buttons (%d)\n", errno); + return -1; + } + return num_buttons; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_LinuxJoystickDevice_nGetNumAxes(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + __u8 num_axes; + if (ioctl(fd, JSIOCGAXES, &num_axes) == -1) { + throwIOException(env, "Failed to get number of buttons (%d)\n", errno); + return -1; + } + return num_axes; +} + +JNIEXPORT jbyteArray JNICALL Java_net_java_games_input_LinuxJoystickDevice_nGetAxisMap(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + __u8 axis_map[ABS_MAX + 1]; + if (ioctl(fd, JSIOCGAXMAP, axis_map) == -1) { + throwIOException(env, "Failed to get axis map (%d)\n", errno); + return NULL; + } + + jbyteArray axis_map_array = (*env)->NewByteArray(env, (ABS_MAX + 1)); + if (axis_map_array == NULL) + return NULL; + (*env)->SetByteArrayRegion(env, axis_map_array, 0, (ABS_MAX + 1), (jbyte *)axis_map); + return axis_map_array; +} + +JNIEXPORT jcharArray JNICALL Java_net_java_games_input_LinuxJoystickDevice_nGetButtonMap(JNIEnv *env, jclass unused, jlong fd_address) { + int fd = (int)fd_address; + __u16 button_map[KEY_MAX - BTN_MISC + 1]; + if (ioctl(fd, JSIOCGBTNMAP, button_map) == -1) { + throwIOException(env, "Failed to get button map (%d)\n", errno); + return NULL; + } + + jcharArray button_map_array = (*env)->NewCharArray(env, (KEY_MAX - BTN_MISC + 1)); + if (button_map_array == NULL) + return NULL; + (*env)->SetCharArrayRegion(env, button_map_array, 0, (KEY_MAX - BTN_MISC + 1), (jchar *)button_map); + return button_map_array; +} + +JNIEXPORT jboolean JNICALL Java_net_java_games_input_LinuxJoystickDevice_nGetNextEvent(JNIEnv *env, jclass unused, jlong fd_address, jobject event_return) { + int fd = (int)fd_address; + jclass event_class = (*env)->GetObjectClass(env, event_return); + if (event_class == NULL) + return JNI_FALSE; + jmethodID event_set = (*env)->GetMethodID(env, event_class, "set", "(JIII)V"); + if (event_set == NULL) + return JNI_FALSE; + struct js_event event; + if (read(fd, &event, sizeof(event)) == -1) { + if (errno == EAGAIN) + return JNI_FALSE; + throwIOException(env, "Failed to read next device event (%d)\n", errno); + return JNI_FALSE; + } + (*env)->CallVoidMethod(env, event_return, event_set, (jlong)event.time, (jint)event.value, (jint)event.type, (jint)event.number); + return JNI_TRUE; +} diff --git a/src/native/osx/build.xml b/src/native/osx/build.xml new file mode 100644 index 0000000..05bda4b --- /dev/null +++ b/src/native/osx/build.xml @@ -0,0 +1,52 @@ + + + OSX JInput Native Plugin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/native/osx/macosxutil.c b/src/native/osx/macosxutil.c new file mode 100644 index 0000000..81b5cc6 --- /dev/null +++ b/src/native/osx/macosxutil.c @@ -0,0 +1,197 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include "util.h" +#include "macosxutil.h" + +typedef struct { + JNIEnv *env; + jobject map; +} dict_context_t; + +typedef struct { + JNIEnv *env; + jobjectArray array; + jsize index; +} array_context_t; + +static jobject createObjectFromCFObject(JNIEnv *env, CFTypeRef cfobject); + +static jstring createStringFromCFString(JNIEnv *env, CFStringRef cfstring) { + CFIndex unicode_length = CFStringGetLength(cfstring); + CFIndex utf8_length = CFStringGetMaximumSizeForEncoding(unicode_length, kCFStringEncodingUTF8); + // Allocate buffer large enough, plus \0 terminator + char *buffer = (char *)malloc(utf8_length + 1); + if (buffer == NULL) + return NULL; + Boolean result = CFStringGetCString(cfstring, buffer, utf8_length + 1, kCFStringEncodingUTF8); + if (!result) { + free(buffer); + return NULL; + } + jstring str = (*env)->NewStringUTF(env, buffer); + free(buffer); + return str; +} + +static jobject createDoubleObjectFromCFNumber(JNIEnv *env, CFNumberRef cfnumber) { + double value; + Boolean result = CFNumberGetValue(cfnumber, kCFNumberDoubleType, &value); + if (!result) + return NULL; + return newJObject(env, "java/lang/Double", "(D)V", (jdouble)value); +} + +static jobject createLongObjectFromCFNumber(JNIEnv *env, CFNumberRef cfnumber) { + SInt64 value; + Boolean result = CFNumberGetValue(cfnumber, kCFNumberSInt64Type, &value); + if (!result) + return NULL; + return newJObject(env, "java/lang/Long", "(J)V", (jlong)value); +} + +static jobject createNumberFromCFNumber(JNIEnv *env, CFNumberRef cfnumber) { + CFNumberType number_type = CFNumberGetType(cfnumber); + switch (number_type) { + case kCFNumberSInt8Type: + case kCFNumberSInt16Type: + case kCFNumberSInt32Type: + case kCFNumberSInt64Type: + case kCFNumberCharType: + case kCFNumberShortType: + case kCFNumberIntType: + case kCFNumberLongType: + case kCFNumberLongLongType: + case kCFNumberCFIndexType: + return createLongObjectFromCFNumber(env, cfnumber); + case kCFNumberFloat32Type: + case kCFNumberFloat64Type: + case kCFNumberFloatType: + case kCFNumberDoubleType: + return createDoubleObjectFromCFNumber(env, cfnumber); + default: + return NULL; + } +} + +static void createArrayEntries(const void *value, void *context) { + array_context_t *array_context = (array_context_t *)context; + jobject jval = createObjectFromCFObject(array_context->env, value); + (*array_context->env)->SetObjectArrayElement(array_context->env, array_context->array, array_context->index++, jval); + (*array_context->env)->DeleteLocalRef(array_context->env, jval); +} + +static jobject createArrayFromCFArray(JNIEnv *env, CFArrayRef cfarray) { + jclass Object_class = (*env)->FindClass(env, "java/lang/Object"); + if (Object_class == NULL) + return NULL; + CFIndex size = CFArrayGetCount(cfarray); + CFRange range = {0, size}; + jobjectArray array = (*env)->NewObjectArray(env, size, Object_class, NULL); + array_context_t array_context; + array_context.env = env; + array_context.array = array; + array_context.index = 0; + CFArrayApplyFunction(cfarray, range, createArrayEntries, &array_context); + return array; +} + +static jobject createObjectFromCFObject(JNIEnv *env, CFTypeRef cfobject) { + CFTypeID type_id = CFGetTypeID(cfobject); + if (type_id == CFDictionaryGetTypeID()) { + return createMapFromCFDictionary(env, cfobject); + } else if (type_id == CFArrayGetTypeID()) { + return createArrayFromCFArray(env, cfobject); + } else if (type_id == CFStringGetTypeID()) { + return createStringFromCFString(env, cfobject); + } else if (type_id == CFNumberGetTypeID()) { + return createNumberFromCFNumber(env, cfobject); + } else { + return NULL; + } +} + +static void createMapKeys(const void *key, const void *value, void *context) { + dict_context_t *dict_context = (dict_context_t *)context; + + jclass Map_class = (*dict_context->env)->GetObjectClass(dict_context->env, dict_context->map); + if (Map_class == NULL) + return; + jmethodID map_put = (*dict_context->env)->GetMethodID(dict_context->env, Map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + if (map_put == NULL) + return; + jobject jkey = createObjectFromCFObject(dict_context->env, key); + jobject jvalue = createObjectFromCFObject(dict_context->env, value); + if (jkey == NULL || jvalue == NULL) + return; + (*dict_context->env)->CallObjectMethod(dict_context->env, dict_context->map, map_put, jkey, jvalue); + (*dict_context->env)->DeleteLocalRef(dict_context->env, jkey); + (*dict_context->env)->DeleteLocalRef(dict_context->env, jvalue); +} + +jobject createMapFromCFDictionary(JNIEnv *env, CFDictionaryRef dict) { + jobject map = newJObject(env, "java/util/HashMap", "()V"); + if (map == NULL) + return NULL; + dict_context_t dict_context; + dict_context.env = env; + dict_context.map = map; + CFDictionaryApplyFunction(dict, createMapKeys, &dict_context); + return map; +} + +void copyEvent(JNIEnv *env, IOHIDEventStruct *event, jobject event_return) { + jclass OSXEvent_class = (*env)->GetObjectClass(env, event_return); + if (OSXEvent_class == NULL) { + return; + } + + jmethodID OSXEvent_set = (*env)->GetMethodID(env, OSXEvent_class, "set", "(JJIJ)V"); + if (OSXEvent_set == NULL) { + return; + } + Nanoseconds nanos = AbsoluteToNanoseconds(event->timestamp); + uint64_t nanos64= *((uint64_t *)&nanos); + (*env)->CallVoidMethod(env, event_return, OSXEvent_set, (jlong)event->type, (jlong)(intptr_t)event->elementCookie, (jint)event->value, (jlong)nanos64); +} diff --git a/src/native/osx/macosxutil.h b/src/native/osx/macosxutil.h new file mode 100644 index 0000000..ed2722c --- /dev/null +++ b/src/native/osx/macosxutil.h @@ -0,0 +1,48 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +extern jobject createMapFromCFDictionary(JNIEnv *env, CFDictionaryRef dict); +extern void copyEvent(JNIEnv *env, IOHIDEventStruct *event, jobject event_return); diff --git a/src/native/osx/net_java_games_input_OSXHIDDevice.c b/src/native/osx/net_java_games_input_OSXHIDDevice.c new file mode 100644 index 0000000..cef6701 --- /dev/null +++ b/src/native/osx/net_java_games_input_OSXHIDDevice.c @@ -0,0 +1,118 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "net_java_games_input_OSXHIDDevice.h" +#include "util.h" +#include "macosxutil.h" + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDDevice_nReleaseDevice(JNIEnv *env, jclass unused, jlong device_address, jlong interface_address) { + io_object_t hidDevice = (io_object_t)device_address; + IOHIDDeviceInterface **device_interface = (IOHIDDeviceInterface **)(intptr_t)interface_address;; + (*device_interface)->Release(device_interface); + IOObjectRelease(hidDevice); +} + +JNIEXPORT jobject JNICALL Java_net_java_games_input_OSXHIDDevice_nGetDeviceProperties(JNIEnv *env, jclass unused, jlong device_address) { + io_object_t hidDevice = (io_object_t)device_address; + CFMutableDictionaryRef properties; + + kern_return_t result = IORegistryEntryCreateCFProperties(hidDevice, + &properties, + kCFAllocatorDefault, + kNilOptions); + if (result != KERN_SUCCESS) { + throwIOException(env, "Failed to create properties for device (%ld)", result); + return NULL; + } + jobject map = createMapFromCFDictionary(env, properties); + CFRelease(properties); + return map; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDDevice_nOpen + (JNIEnv * env, jclass unused, jlong lpDevice) { + IOHIDDeviceInterface **hidDeviceInterface = (IOHIDDeviceInterface **)(intptr_t)lpDevice; + IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, 0); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Device open failed: %d", ioReturnValue); + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDDevice_nClose + (JNIEnv * env, jclass unused, jlong lpDevice) { + IOHIDDeviceInterface **hidDeviceInterface = (IOHIDDeviceInterface **)(intptr_t)lpDevice; + IOReturn ioReturnValue = (*hidDeviceInterface)->close(hidDeviceInterface); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Device close failed: %d", ioReturnValue); + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDDevice_nGetElementValue + (JNIEnv * env, jclass unused, jlong lpDevice, jlong hidCookie, jobject event_return) { + IOHIDDeviceInterface **hidDeviceInterface = (IOHIDDeviceInterface **)(intptr_t)lpDevice; + IOHIDElementCookie cookie = (IOHIDElementCookie)(intptr_t)hidCookie; + IOHIDEventStruct event; + + IOReturn ioReturnValue = (*hidDeviceInterface)->getElementValue(hidDeviceInterface, cookie, &event); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Device getElementValue failed: %d", ioReturnValue); + return; + } + copyEvent(env, &event, event_return); + if (event.longValue != NULL) { + free(event.longValue); + } +} + +JNIEXPORT jlong JNICALL Java_net_java_games_input_OSXHIDDevice_nCreateQueue(JNIEnv *env, jclass unused, jlong device_address) { + IOHIDDeviceInterface **hidDeviceInterface = (IOHIDDeviceInterface **)(intptr_t)device_address; + IOHIDQueueInterface **queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); + + if (queue == NULL) { + throwIOException(env, "Could not allocate queue"); + return 0; + } + return (jlong)(intptr_t)queue; +} diff --git a/src/native/osx/net_java_games_input_OSXHIDDeviceIterator.c b/src/native/osx/net_java_games_input_OSXHIDDeviceIterator.c new file mode 100644 index 0000000..e76363f --- /dev/null +++ b/src/native/osx/net_java_games_input_OSXHIDDeviceIterator.c @@ -0,0 +1,143 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "net_java_games_input_OSXHIDDeviceIterator.h" +#include "util.h" + +JNIEXPORT jlong JNICALL Java_net_java_games_input_OSXHIDDeviceIterator_nCreateIterator(JNIEnv *env, jclass unused) { + io_iterator_t hidObjectIterator; + // Set up a matching dictionary to search the I/O Registry by class + // name for all HID class devices + // + CFMutableDictionaryRef hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey); + + // Now search I/O Registry for matching devices. + // IOServiceGetMatchingServices consumes a reference to the dictionary so we don't have to release it + IOReturn ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); + + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Failed to create iterator (%ld)\n", ioReturnValue); + return 0; + } + + if (hidObjectIterator == IO_OBJECT_NULL) { + throwIOException(env, "Failed to create iterator\n"); + return 0; + } + return (jlong)hidObjectIterator; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDDeviceIterator_nReleaseIterator(JNIEnv *env, jclass unused, jlong address) { + io_iterator_t iterator = (io_iterator_t)address; + IOObjectRelease(iterator); +} + +static IOHIDDeviceInterface **createHIDDevice(JNIEnv *env, io_object_t hidDevice) { +// io_name_t className; + IOHIDDeviceInterface **hidDeviceInterface; + IOCFPlugInInterface **plugInInterface; + SInt32 score; + +/* ioReturnValue = IOObjectGetClass(hidDevice, className); + if (ioReturnValue != kIOReturnSuccess) { + printfJava(env, "Failed to get IOObject class name."); + } + + printfJava(env, "Found device type [%s]\n", className); + */ + IOReturn ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice, + kIOHIDDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugInInterface, + &score); + + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Couldn't create plugin for device interface (%ld)\n", ioReturnValue); + return NULL; + } + //Call a method of the intermediate plug-in to create the device + //interface + // + HRESULT plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), + (LPVOID)&hidDeviceInterface); + (*plugInInterface)->Release(plugInInterface); + if (plugInResult != S_OK) { + throwIOException(env, "Couldn't create HID class device interface (%ld)\n", plugInResult); + return NULL; + } + return hidDeviceInterface; +} + +JNIEXPORT jobject JNICALL Java_net_java_games_input_OSXHIDDeviceIterator_nNext(JNIEnv *env, jclass unused, jlong address) { + io_iterator_t iterator = (io_iterator_t)address; + io_object_t hidDevice; +// io_string_t path; +// kern_return_t result; + + hidDevice = IOIteratorNext(iterator); + if (hidDevice == MACH_PORT_NULL) + return NULL; +/* IOResult result = IORegistryEntryGetPath(hidDevice, kIOServicePlane, path); + + if (result != KERN_SUCCESS) { + IOObjectRelease(hidDevice); + throwIOException("Failed to get device path (%ld)\n", result); + return NULL; + } +*/ + IOHIDDeviceInterface **device_interface = createHIDDevice(env, hidDevice); + if (device_interface == NULL) { + IOObjectRelease(hidDevice); + return NULL; + } + jobject device_object = newJObject(env, "net/java/games/input/OSXHIDDevice", "(JJ)V", (jlong)hidDevice, (jlong)(intptr_t)device_interface); + if (device_object == NULL) { + (*device_interface)->Release(device_interface); + IOObjectRelease(hidDevice); + return NULL; + } + return device_object; +} diff --git a/src/native/osx/net_java_games_input_OSXHIDQueue.c b/src/native/osx/net_java_games_input_OSXHIDQueue.c new file mode 100644 index 0000000..12dbd0a --- /dev/null +++ b/src/native/osx/net_java_games_input_OSXHIDQueue.c @@ -0,0 +1,135 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "net_java_games_input_OSXHIDQueue.h" +#include "util.h" +#include "macosxutil.h" + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDQueue_nOpen(JNIEnv *env, jclass unused, jlong address, jint queue_depth) { + IOHIDQueueInterface **queue = (IOHIDQueueInterface **)(intptr_t)address; + IOReturn ioReturnValue = (*queue)->create(queue, 0, queue_depth); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Queue open failed: %d\n", ioReturnValue); + return; + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDQueue_nStart(JNIEnv *env, jclass unused, jlong address) { + IOHIDQueueInterface **queue = (IOHIDQueueInterface **)(intptr_t)address; + IOReturn ioReturnValue = (*queue)->start(queue); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Queue start failed: %d\n", ioReturnValue); + return; + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDQueue_nStop(JNIEnv *env, jclass unused, jlong address) { + IOHIDQueueInterface **queue = (IOHIDQueueInterface **)(intptr_t)address; + IOReturn ioReturnValue = (*queue)->stop(queue); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Queue stop failed: %d\n", ioReturnValue); + return; + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDQueue_nClose(JNIEnv *env, jclass unused, jlong address) { + IOHIDQueueInterface **queue = (IOHIDQueueInterface **)(intptr_t)address; + IOReturn ioReturnValue = (*queue)->dispose(queue); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Queue dispose failed: %d\n", ioReturnValue); + return; + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDQueue_nReleaseQueue(JNIEnv *env, jclass unused, jlong address) { + IOHIDQueueInterface **queue = (IOHIDQueueInterface **)(intptr_t)address; + IOReturn ioReturnValue = (*queue)->Release(queue); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Queue Release failed: %d\n", ioReturnValue); + return; + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDQueue_nAddElement(JNIEnv *env, jclass unused, jlong address, jlong cookie_address) { + IOHIDQueueInterface **queue = (IOHIDQueueInterface **)(intptr_t)address; + IOHIDElementCookie cookie = (IOHIDElementCookie)(intptr_t)cookie_address; + + IOReturn ioReturnValue = (*queue)->addElement(queue, cookie, 0); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Queue addElement failed: %d\n", ioReturnValue); + return; + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_OSXHIDQueue_nRemoveElement(JNIEnv *env, jclass unused, jlong address, jlong cookie_address) { + IOHIDQueueInterface **queue = (IOHIDQueueInterface **)(intptr_t)address; + IOHIDElementCookie cookie = (IOHIDElementCookie)(intptr_t)cookie_address; + + IOReturn ioReturnValue = (*queue)->removeElement(queue, cookie); + if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Queue removeElement failed: %d\n", ioReturnValue); + return; + } +} + + +JNIEXPORT jboolean JNICALL Java_net_java_games_input_OSXHIDQueue_nGetNextEvent(JNIEnv *env, jclass unused, jlong address, jobject event_return) { + IOHIDQueueInterface **queue = (IOHIDQueueInterface **)(intptr_t)address; + IOHIDEventStruct event; + + AbsoluteTime zeroTime = {0, 0}; + IOReturn ioReturnValue = (*queue)->getNextEvent(queue, &event, zeroTime, 0); + if (ioReturnValue == kIOReturnUnderrun) { + return JNI_FALSE; + } else if (ioReturnValue != kIOReturnSuccess) { + throwIOException(env, "Queue getNextEvent failed: %d\n", ioReturnValue); + return JNI_FALSE; + } + copyEvent(env, &event, event_return); + if (event.longValue != NULL) { + free(event.longValue); + } + return JNI_TRUE; +} diff --git a/src/native/windows/build.xml b/src/native/windows/build.xml new file mode 100644 index 0000000..412a9b7 --- /dev/null +++ b/src/native/windows/build.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/native/windows/dx8/dxversion.h b/src/native/windows/dx8/dxversion.h new file mode 100644 index 0000000..396c7f7 --- /dev/null +++ b/src/native/windows/dx8/dxversion.h @@ -0,0 +1,13 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#ifndef DXVERSION_H +#define DXVERSION_H + +#define DIRECTINPUT_VERSION 0x0800 + +#endif diff --git a/src/native/windows/dx8/net_java_games_input_IDirectInput.c b/src/native/windows/dx8/net_java_games_input_IDirectInput.c new file mode 100644 index 0000000..2a8df2b --- /dev/null +++ b/src/native/windows/dx8/net_java_games_input_IDirectInput.c @@ -0,0 +1,103 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#include +#include +#include "dxversion.h" +#include +#include "net_java_games_input_IDirectInput.h" +#include "util.h" +#include "winutil.h" + +typedef struct { + LPDIRECTINPUT8 lpDirectInput; + JNIEnv *env; + jobject obj; +} enum_context_t; + +JNIEXPORT jlong JNICALL Java_net_java_games_input_IDirectInput_createIDirectInput(JNIEnv *env, jclass unused) { + LPDIRECTINPUT8 lpDirectInput; + HRESULT res = DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, + &IID_IDirectInput8, (void *)&lpDirectInput, NULL); + if (FAILED(res)) { + throwIOException(env, "Failed to create IDirectInput8 (%d)\n", res); + return 0; + } + return (jlong)(INT_PTR)lpDirectInput; +} + +static BOOL CALLBACK enumerateDevicesCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID context) { + enum_context_t *enum_context = (enum_context_t *)context; +// LPCDIDATAFORMAT lpDataFormat; + LPDIRECTINPUTDEVICE8 lpDevice; + DWORD device_type; + DWORD device_subtype; + HRESULT res; + jclass obj_class; + jmethodID IDirectInput_addDevice; + jstring instance_name; + jstring product_name; + jbyteArray instance_guid; + jbyteArray product_guid; + + instance_guid = wrapGUID(enum_context->env, &(lpddi->guidInstance)); + if (instance_guid == NULL) + return DIENUM_STOP; + product_guid = wrapGUID(enum_context->env, &(lpddi->guidProduct)); + if (product_guid == NULL) + return DIENUM_STOP; + instance_name = (*enum_context->env)->NewStringUTF(enum_context->env, lpddi->tszInstanceName); + if (instance_name == NULL) + return DIENUM_STOP; + product_name = (*enum_context->env)->NewStringUTF(enum_context->env, lpddi->tszProductName); + if (product_name == NULL) + return DIENUM_STOP; + + obj_class = (*enum_context->env)->GetObjectClass(enum_context->env, enum_context->obj); + if (obj_class == NULL) + return DIENUM_STOP; + + IDirectInput_addDevice = (*enum_context->env)->GetMethodID(enum_context->env, obj_class, "addDevice", "(J[B[BIILjava/lang/String;Ljava/lang/String;)V"); + if (IDirectInput_addDevice == NULL) + return DIENUM_STOP; + + res = IDirectInput8_CreateDevice(enum_context->lpDirectInput, &(lpddi->guidInstance), &lpDevice, NULL); + if (FAILED(res)) { + throwIOException(enum_context->env, "Failed to create device (%d)\n", res); + return DIENUM_STOP; + } + + device_type = GET_DIDEVICE_TYPE(lpddi->dwDevType); + device_subtype = GET_DIDEVICE_SUBTYPE(lpddi->dwDevType); + + (*enum_context->env)->CallVoidMethod(enum_context->env, enum_context->obj, IDirectInput_addDevice, (jlong)(INT_PTR)lpDevice, instance_guid, product_guid, (jint)device_type, (jint)device_subtype, instance_name, product_name); + if ((*enum_context->env)->ExceptionOccurred(enum_context->env) != NULL) { + IDirectInputDevice8_Release(lpDevice); + return DIENUM_STOP; + } + + return DIENUM_CONTINUE; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_IDirectInput_nEnumDevices(JNIEnv *env, jobject obj, jlong address) { + LPDIRECTINPUT8 lpDirectInput = (LPDIRECTINPUT8)(INT_PTR)address; + HRESULT res; + + enum_context_t enum_context; + enum_context.lpDirectInput = lpDirectInput; + enum_context.env = env; + enum_context.obj = obj; + res = IDirectInput8_EnumDevices(lpDirectInput, DI8DEVCLASS_ALL, enumerateDevicesCallback, &enum_context, DIEDFL_ATTACHEDONLY); + if (FAILED(res)) { + throwIOException(env, "Failed to enumerate devices (%d)\n", res); + } +} + +JNIEXPORT void JNICALL Java_net_java_games_input_IDirectInput_nRelease(JNIEnv *env, jclass unused, jlong address) { + LPDIRECTINPUT8 lpDirectInput = (LPDIRECTINPUT8)(INT_PTR)address; + IDirectInput8_Release(lpDirectInput); +} diff --git a/src/native/windows/dx8/net_java_games_input_IDirectInputDevice.c b/src/native/windows/dx8/net_java_games_input_IDirectInputDevice.c new file mode 100644 index 0000000..9f29191 --- /dev/null +++ b/src/native/windows/dx8/net_java_games_input_IDirectInputDevice.c @@ -0,0 +1,502 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#include +#include "dxversion.h" +#include +#include +#include "net_java_games_input_IDirectInputDevice.h" +#include "util.h" +#include "winutil.h" + +typedef struct { + JNIEnv *env; + jobject device_obj; +} enum_context_t; + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nSetBufferSize(JNIEnv *env, jclass unused, jlong address, jint size) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + DIPROPDWORD dipropdw; + HRESULT res; + + dipropdw.diph.dwSize = sizeof(DIPROPDWORD); + dipropdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + dipropdw.diph.dwObj = 0; + dipropdw.diph.dwHow = DIPH_DEVICE; + dipropdw.dwData = size; + res = IDirectInputDevice8_SetProperty(lpDevice, DIPROP_BUFFERSIZE, &dipropdw.diph); + return res; +} + +static jint mapGUIDType(const GUID *guid) { + if (IsEqualGUID(guid, &GUID_XAxis)) { + return net_java_games_input_IDirectInputDevice_GUID_XAxis; + } else if (IsEqualGUID(guid, &GUID_YAxis)) { + return net_java_games_input_IDirectInputDevice_GUID_YAxis; + } else if (IsEqualGUID(guid, &GUID_ZAxis)) { + return net_java_games_input_IDirectInputDevice_GUID_ZAxis; + } else if (IsEqualGUID(guid, &GUID_RxAxis)) { + return net_java_games_input_IDirectInputDevice_GUID_RxAxis; + } else if (IsEqualGUID(guid, &GUID_RyAxis)) { + return net_java_games_input_IDirectInputDevice_GUID_RyAxis; + } else if (IsEqualGUID(guid, &GUID_RzAxis)) { + return net_java_games_input_IDirectInputDevice_GUID_RzAxis; + } else if (IsEqualGUID(guid, &GUID_Slider)) { + return net_java_games_input_IDirectInputDevice_GUID_Slider; + } else if (IsEqualGUID(guid, &GUID_Button)) { + return net_java_games_input_IDirectInputDevice_GUID_Button; + } else if (IsEqualGUID(guid, &GUID_Key)) { + return net_java_games_input_IDirectInputDevice_GUID_Key; + } else if (IsEqualGUID(guid, &GUID_POV)) { + return net_java_games_input_IDirectInputDevice_GUID_POV; + } else if (IsEqualGUID(guid, &GUID_ConstantForce)) { + return net_java_games_input_IDirectInputDevice_GUID_ConstantForce; + } else if (IsEqualGUID(guid, &GUID_RampForce)) { + return net_java_games_input_IDirectInputDevice_GUID_RampForce; + } else if (IsEqualGUID(guid, &GUID_Square)) { + return net_java_games_input_IDirectInputDevice_GUID_Square; + } else if (IsEqualGUID(guid, &GUID_Sine)) { + return net_java_games_input_IDirectInputDevice_GUID_Sine; + } else if (IsEqualGUID(guid, &GUID_Triangle)) { + return net_java_games_input_IDirectInputDevice_GUID_Triangle; + } else if (IsEqualGUID(guid, &GUID_SawtoothUp)) { + return net_java_games_input_IDirectInputDevice_GUID_SawtoothUp; + } else if (IsEqualGUID(guid, &GUID_SawtoothDown)) { + return net_java_games_input_IDirectInputDevice_GUID_SawtoothDown; + } else if (IsEqualGUID(guid, &GUID_Spring)) { + return net_java_games_input_IDirectInputDevice_GUID_Spring; + } else if (IsEqualGUID(guid, &GUID_Damper)) { + return net_java_games_input_IDirectInputDevice_GUID_Damper; + } else if (IsEqualGUID(guid, &GUID_Inertia)) { + return net_java_games_input_IDirectInputDevice_GUID_Inertia; + } else if (IsEqualGUID(guid, &GUID_Friction)) { + return net_java_games_input_IDirectInputDevice_GUID_Friction; + } else if (IsEqualGUID(guid, &GUID_CustomForce)) { + return net_java_games_input_IDirectInputDevice_GUID_CustomForce; + } else + return net_java_games_input_IDirectInputDevice_GUID_Unknown; +} + +static BOOL CALLBACK enumEffectsCallback(LPCDIEFFECTINFO pdei, LPVOID pvRef) { + enum_context_t *enum_context = (enum_context_t *)pvRef; + jmethodID add_method; + jstring name; + jbyteArray guid; + JNIEnv *env = enum_context->env; + jobject device_obj = enum_context->device_obj; + jint guid_id; + jclass obj_class = (*env)->GetObjectClass(env, device_obj); + + + if (obj_class == NULL) + return DIENUM_STOP; + guid = wrapGUID(env, &(pdei->guid)); + if (guid == NULL) + return DIENUM_STOP; + add_method = (*env)->GetMethodID(env, obj_class, "addEffect", "([BIIIILjava/lang/String;)V"); + if (add_method == NULL) + return DIENUM_STOP; + name = (*env)->NewStringUTF(env, pdei->tszName); + if (name == NULL) + return DIENUM_STOP; + guid_id = mapGUIDType(&(pdei->guid)); + (*env)->CallBooleanMethod(env, device_obj, add_method, guid, guid_id, (jint)pdei->dwEffType, (jint)pdei->dwStaticParams, (jint)pdei->dwDynamicParams, name); + if ((*env)->ExceptionOccurred(env)) { + return DIENUM_STOP; + } + return DIENUM_CONTINUE; +} + +static BOOL CALLBACK enumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) { + enum_context_t *enum_context = (enum_context_t *)pvRef; + jmethodID add_method; + jstring name; + DWORD instance; + DWORD type; + jint guid_type; + jbyteArray guid; + JNIEnv *env = enum_context->env; + jobject device_obj = enum_context->device_obj; + jclass obj_class = (*env)->GetObjectClass(env, device_obj); + + if (obj_class == NULL) + return DIENUM_STOP; + guid = wrapGUID(env, &(lpddoi->guidType)); + if (guid == NULL) + return DIENUM_STOP; + add_method = (*env)->GetMethodID(env, obj_class, "addObject", "([BIIIIILjava/lang/String;)V"); + if (add_method == NULL) + return DIENUM_STOP; + name = (*env)->NewStringUTF(env, lpddoi->tszName); + if (name == NULL) + return DIENUM_STOP; + instance = DIDFT_GETINSTANCE(lpddoi->dwType); + type = DIDFT_GETTYPE(lpddoi->dwType); + guid_type = mapGUIDType(&(lpddoi->guidType)); +//printfJava(env, "name %s guid_type %d id %d\n", lpddoi->tszName, guid_type, lpddoi->dwType); + (*env)->CallBooleanMethod(env, device_obj, add_method, guid, (jint)guid_type, (jint)lpddoi->dwType, (jint)type, (jint)instance, (jint)lpddoi->dwFlags, name); + if ((*env)->ExceptionOccurred(env)) { + return DIENUM_STOP; + } + return DIENUM_CONTINUE; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nGetRangeProperty(JNIEnv *env, jclass unused, jlong address, jint object_id, jlongArray range_array_obj) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + DIPROPRANGE range; + HRESULT res; + jlong range_array[2]; + + range.diph.dwSize = sizeof(DIPROPRANGE); + range.diph.dwHeaderSize = sizeof(DIPROPHEADER); + range.diph.dwObj = object_id; + range.diph.dwHow = DIPH_BYID; + res = IDirectInputDevice8_GetProperty(lpDevice, DIPROP_RANGE, &(range.diph)); + range_array[0] = range.lMin; + range_array[1] = range.lMax; + (*env)->SetLongArrayRegion(env, range_array_obj, 0, 2, range_array); + return res; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nGetDeadzoneProperty(JNIEnv *env, jclass unused, jlong address, jint object_id) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + DIPROPDWORD deadzone; + HRESULT res; + + deadzone.diph.dwSize = sizeof(deadzone); + deadzone.diph.dwHeaderSize = sizeof(DIPROPHEADER); + deadzone.diph.dwObj = object_id; + deadzone.diph.dwHow = DIPH_BYID; + res = IDirectInputDevice8_GetProperty(lpDevice, DIPROP_DEADZONE, &(deadzone.diph)); + if (res != DI_OK && res != S_FALSE) + throwIOException(env, "Failed to get deadzone property (%x)\n", res); + return deadzone.dwData; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nSetDataFormat(JNIEnv *env, jclass unused, jlong address, jint flags, jobjectArray objects) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + DIDATAFORMAT data_format; + jsize num_objects = (*env)->GetArrayLength(env, objects); + /* + * Data size must be a multiple of 4, but since sizeof(jint) is + * 4, we're safe + */ + DWORD data_size = num_objects*sizeof(jint); + GUID *guids; + DIOBJECTDATAFORMAT *object_formats; + int i; + HRESULT res; + jclass clazz; + jmethodID getGUID_method; + jmethodID getFlags_method; + jmethodID getType_method; + jmethodID getInstance_method; + jobject object; + jint type; + jint object_flags; + jint instance; + jobject guid_array; + DWORD composite_type; + DWORD flags_masked; + LPDIOBJECTDATAFORMAT object_format; + + data_format.dwSize = sizeof(DIDATAFORMAT); + data_format.dwObjSize = sizeof(DIOBJECTDATAFORMAT); + data_format.dwFlags = flags; + data_format.dwDataSize = data_size; + data_format.dwNumObjs = num_objects; + + clazz = (*env)->FindClass(env, "net/java/games/input/DIDeviceObject"); + if (clazz == NULL) + return -1; + getGUID_method = (*env)->GetMethodID(env, clazz, "getGUID", "()[B"); + if (getGUID_method == NULL) + return -1; + getFlags_method = (*env)->GetMethodID(env, clazz, "getFlags", "()I"); + if (getFlags_method == NULL) + return -1; + getType_method = (*env)->GetMethodID(env, clazz, "getType", "()I"); + if (getType_method == NULL) + return -1; + getInstance_method = (*env)->GetMethodID(env, clazz, "getInstance", "()I"); + if (getInstance_method == NULL) + return -1; + + guids = (GUID *)malloc(num_objects*sizeof(GUID)); + if (guids == NULL) { + throwIOException(env, "Failed to allocate GUIDs"); + return -1; + } + object_formats = (DIOBJECTDATAFORMAT *)malloc(num_objects*sizeof(DIOBJECTDATAFORMAT)); + if (object_formats == NULL) { + free(guids); + throwIOException(env, "Failed to allocate data format"); + return -1; + } + for (i = 0; i < num_objects; i++) { + object = (*env)->GetObjectArrayElement(env, objects, i); + if ((*env)->ExceptionOccurred(env)) { + free(guids); + free(object_formats); + return -1; + } + guid_array = (*env)->CallObjectMethod(env, object, getGUID_method); + if ((*env)->ExceptionOccurred(env)) { + free(guids); + free(object_formats); + return -1; + } + unwrapGUID(env, guid_array, guids + i); + if ((*env)->ExceptionOccurred(env)) { + free(guids); + free(object_formats); + return -1; + } + type = (*env)->CallIntMethod(env, object, getType_method); + if ((*env)->ExceptionOccurred(env)) { + free(guids); + free(object_formats); + return -1; + } + object_flags = (*env)->CallIntMethod(env, object, getFlags_method); + if ((*env)->ExceptionOccurred(env)) { + free(guids); + free(object_formats); + return -1; + } + instance = (*env)->CallIntMethod(env, object, getInstance_method); + if ((*env)->ExceptionOccurred(env)) { + free(guids); + free(object_formats); + return -1; + } + (*env)->DeleteLocalRef(env, object); + composite_type = type | DIDFT_MAKEINSTANCE(instance); + flags_masked = flags & (DIDOI_ASPECTACCEL | DIDOI_ASPECTFORCE | DIDOI_ASPECTPOSITION | DIDOI_ASPECTVELOCITY); + object_format = object_formats + i; + object_format->pguid = guids + i; + object_format->dwType = composite_type; + object_format->dwFlags = flags_masked; + // dwOfs must be multiple of 4, but sizeof(jint) is 4, so we're safe + object_format->dwOfs = i*sizeof(jint); + } + data_format.rgodf = object_formats; + res = IDirectInputDevice8_SetDataFormat(lpDevice, &data_format); + free(guids); + free(object_formats); + return res; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nAcquire(JNIEnv *env, jclass unused, jlong address) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + + HRESULT res = IDirectInputDevice8_Acquire(lpDevice); + return res; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nUnacquire(JNIEnv *env, jclass unused, jlong address) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + + HRESULT res = IDirectInputDevice8_Unacquire(lpDevice); + return res; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nPoll(JNIEnv *env, jclass unused, jlong address) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + + HRESULT res = IDirectInputDevice8_Poll(lpDevice); + return res; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nGetDeviceState(JNIEnv *env, jclass unused, jlong address, jintArray device_state_array) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + jsize state_length = (*env)->GetArrayLength(env, device_state_array); + DWORD state_size = state_length*sizeof(jint); + HRESULT res; + jint *device_state = (*env)->GetIntArrayElements(env, device_state_array, NULL); + if (device_state == NULL) + return -1; + + res = IDirectInputDevice8_GetDeviceState(lpDevice, state_size, device_state); + (*env)->ReleaseIntArrayElements(env, device_state_array, device_state, 0); + return res; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nGetDeviceData(JNIEnv *env, jclass unused, jlong address, jint flags, jobject queue, jobject queue_array, jint position, jint remaining) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + DWORD num_events = remaining; + DIDEVICEOBJECTDATA *data; + DIDEVICEOBJECTDATA *data_element; + jmethodID set_method; + HRESULT res; + int i; + jclass data_class; + jclass queue_class; + jmethodID position_method; + + data_class = (*env)->FindClass(env, "net/java/games/input/DIDeviceObjectData"); + if (data_class == NULL) + return -1; + set_method = (*env)->GetMethodID(env, data_class, "set", "(IIII)V"); + if (set_method == NULL) + return -1; + queue_class = (*env)->GetObjectClass(env, queue); + if (queue_class == NULL) + return -1; + position_method = (*env)->GetMethodID(env, queue_class, "position", "(I)V"); + if (position_method == NULL) + return -1; + + data = (DIDEVICEOBJECTDATA *)malloc(num_events*sizeof(DIDEVICEOBJECTDATA)); + if (data == NULL) + return -1; + + res = IDirectInputDevice8_GetDeviceData(lpDevice, sizeof(DIDEVICEOBJECTDATA), data, &num_events, flags); + if (res == DI_OK || res == DI_BUFFEROVERFLOW) { + for (i = 0; i < num_events; i++) { + jobject queue_element = (*env)->GetObjectArrayElement(env, queue_array, position + i); + if (queue_element == NULL) { + free(data); + return -1; + } + data_element = data + i; + (*env)->CallVoidMethod(env, queue_element, set_method, (jint)data_element->dwOfs, (jint)data_element->dwData, (jint)data_element->dwTimeStamp, (jint)data_element->dwSequence); + if ((*env)->ExceptionOccurred(env)) { + free(data); + return -1; + } + } + (*env)->CallVoidMethod(env, queue, position_method, position + num_events); + } + free(data); + return res; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nEnumEffects(JNIEnv *env, jobject device_obj, jlong address, jint flags) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + HRESULT res; + enum_context_t enum_context; + + enum_context.env = env; + enum_context.device_obj = device_obj; + res = IDirectInputDevice8_EnumEffects(lpDevice, enumEffectsCallback, &enum_context, flags); + return res; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nEnumObjects(JNIEnv *env, jobject device_obj, jlong address, jint flags) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + HRESULT res; + enum_context_t enum_context; + + enum_context.env = env; + enum_context.device_obj = device_obj; + res = IDirectInputDevice8_EnumObjects(lpDevice, enumObjectsCallback, &enum_context, flags); + return res; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputDevice_nSetCooperativeLevel(JNIEnv *env, jclass unused, jlong address, jlong hwnd_address, jint flags) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + HWND hwnd = (HWND)(INT_PTR)hwnd_address; + + HRESULT res = IDirectInputDevice8_SetCooperativeLevel(lpDevice, hwnd, flags); + return res; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_IDirectInputDevice_nRelease(JNIEnv *env, jclass unused, jlong address) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + + IDirectInputDevice8_Release(lpDevice); +} + +JNIEXPORT jlong JNICALL Java_net_java_games_input_IDirectInputDevice_nCreatePeriodicEffect(JNIEnv *env, jclass unused, jlong address, jbyteArray effect_guid_array, jint flags, jint duration, jint sample_period, jint gain, jint trigger_button, jint trigger_repeat_interval, jintArray axis_ids_array, jlongArray directions_array, jint envelope_attack_level, jint envelope_attack_time, jint envelope_fade_level, jint envelope_fade_time, jint periodic_magnitude, jint periodic_offset, jint periodic_phase, jint periodic_period, jint start_delay) { + LPDIRECTINPUTDEVICE8 lpDevice = (LPDIRECTINPUTDEVICE8)(INT_PTR)address; + LPDIRECTINPUTEFFECT lpdiEffect; + DIEFFECT effect; + GUID effect_guid; + jint *axis_ids; + jlong *directions; + jsize num_axes; + jsize num_directions; + LONG *directions_long; + DWORD *axis_ids_dword; + HRESULT res; + DIPERIODIC periodic; + DIENVELOPE envelope; + int i; + + num_axes = (*env)->GetArrayLength(env, axis_ids_array); + num_directions = (*env)->GetArrayLength(env, directions_array); + + if (num_axes != num_directions) { + throwIOException(env, "axis_ids.length != directions.length\n"); + return 0; + } + + unwrapGUID(env, effect_guid_array, &effect_guid); + if ((*env)->ExceptionOccurred(env)) + return 0; + axis_ids = (*env)->GetIntArrayElements(env, axis_ids_array, NULL); + if (axis_ids == NULL) + return 0; + directions = (*env)->GetLongArrayElements(env, directions_array, NULL); + if (axis_ids == NULL) + return 0; + axis_ids_dword = (DWORD *)malloc(sizeof(DWORD)*num_axes); + if (axis_ids_dword == NULL) { + throwIOException(env, "Failed to allocate axes array\n"); + return 0; + } + directions_long = (LONG *)malloc(sizeof(LONG)*num_directions); + if (directions_long == NULL) { + free(axis_ids_dword); + throwIOException(env, "Failed to allocate directions array\n"); + return 0; + } + for (i = 0; i < num_axes; i++) { + axis_ids_dword[i] = axis_ids[i]; + } + for (i = 0; i < num_directions; i++) { + directions_long[i] = directions[i]; + } + + envelope.dwSize = sizeof(DIENVELOPE); + envelope.dwAttackLevel = envelope_attack_level; + envelope.dwAttackTime = envelope_attack_time; + envelope.dwFadeLevel = envelope_fade_level; + envelope.dwFadeTime = envelope_fade_time; + + periodic.dwMagnitude = periodic_magnitude; + periodic.lOffset = periodic_offset; + periodic.dwPhase = periodic_phase; + periodic.dwPeriod = periodic_period; + + effect.dwSize = sizeof(DIEFFECT); + effect.dwFlags = flags; + effect.dwDuration = duration; + effect.dwSamplePeriod = sample_period; + effect.dwGain = gain; + effect.dwTriggerButton = trigger_button; + effect.dwTriggerRepeatInterval = trigger_repeat_interval; + effect.cAxes = num_axes; + effect.rgdwAxes = axis_ids_dword; + effect.rglDirection = directions_long; + effect.lpEnvelope = &envelope; + effect.cbTypeSpecificParams = sizeof(periodic); + effect.lpvTypeSpecificParams = &periodic; + effect.dwStartDelay = start_delay; + + res = IDirectInputDevice8_CreateEffect(lpDevice, &effect_guid, &effect, &lpdiEffect, NULL); + (*env)->ReleaseIntArrayElements(env, axis_ids_array, axis_ids, 0); + (*env)->ReleaseLongArrayElements(env, directions_array, directions, 0); + free(axis_ids_dword); + free(directions_long); + if (res != DI_OK) { + throwIOException(env, "Failed to create effect (0x%x)\n", res); + return 0; + } + return (jlong)(INT_PTR)lpdiEffect; +} diff --git a/src/native/windows/dx8/net_java_games_input_IDirectInputEffect.c b/src/native/windows/dx8/net_java_games_input_IDirectInputEffect.c new file mode 100644 index 0000000..4553817 --- /dev/null +++ b/src/native/windows/dx8/net_java_games_input_IDirectInputEffect.c @@ -0,0 +1,42 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#include +#include "dxversion.h" +#include +#include +#include "net_java_games_input_IDirectInputEffect.h" +#include "util.h" + +JNIEXPORT void JNICALL Java_net_java_games_input_IDirectInputEffect_nRelease(JNIEnv *env, jclass unused, jlong address) { + LPDIRECTINPUTEFFECT ppdeff = (LPDIRECTINPUTEFFECT)(INT_PTR)address; + + IDirectInputEffect_Release(ppdeff); +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputEffect_nSetGain(JNIEnv *env, jclass unused, jlong address, jint gain) { + LPDIRECTINPUTEFFECT ppdeff = (LPDIRECTINPUTEFFECT)(INT_PTR)address; + DIEFFECT params; + + ZeroMemory(¶ms, sizeof(params)); + params.dwSize = sizeof(params); + params.dwGain = gain; + + return IDirectInputEffect_SetParameters(ppdeff, ¶ms, DIEP_GAIN); +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputEffect_nStart(JNIEnv *env, jclass unused, jlong address, jint iterations, jint flags) { + LPDIRECTINPUTEFFECT ppdeff = (LPDIRECTINPUTEFFECT)(INT_PTR)address; + + return IDirectInputEffect_Start(ppdeff, iterations, flags); +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_IDirectInputEffect_nStop(JNIEnv *env, jclass unused, jlong address) { + LPDIRECTINPUTEFFECT ppdeff = (LPDIRECTINPUTEFFECT)(INT_PTR)address; + + return IDirectInputEffect_Stop(ppdeff); +} diff --git a/src/native/windows/net_java_games_input_DummyWindow.c b/src/native/windows/net_java_games_input_DummyWindow.c new file mode 100644 index 0000000..1aa1832 --- /dev/null +++ b/src/native/windows/net_java_games_input_DummyWindow.c @@ -0,0 +1,71 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#include +#include +#include "net_java_games_input_DummyWindow.h" +#include "util.h" + +static const TCHAR* DUMMY_WINDOW_NAME = "JInputControllerWindow"; + +static LRESULT CALLBACK DummyWndProc( + HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + return DefWindowProc(hWnd, message, wParam, lParam); +} + +static BOOL RegisterDummyWindow(HINSTANCE hInstance) +{ + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = (WNDPROC)DummyWndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = NULL; + wcex.hCursor = NULL; + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = (LPCSTR)NULL; + wcex.lpszClassName = DUMMY_WINDOW_NAME; + wcex.hIconSm = NULL; + return RegisterClassEx(&wcex); +} + +JNIEXPORT jlong JNICALL Java_net_java_games_input_DummyWindow_createWindow(JNIEnv *env, jclass unused) { + HINSTANCE hInst = GetModuleHandle(NULL); + HWND hwndDummy; + WNDCLASSEX class_info; + class_info.cbSize = sizeof(WNDCLASSEX); + class_info.cbClsExtra = 0; + class_info.cbWndExtra = 0; + + if (!GetClassInfoEx(hInst, DUMMY_WINDOW_NAME, &class_info)) { + // Register the dummy input window + if (!RegisterDummyWindow(hInst)) { + throwIOException(env, "Failed to register window class (%d)\n", GetLastError()); + return 0; + } + } + + // Create the dummy input window + hwndDummy = CreateWindow(DUMMY_WINDOW_NAME, NULL, + WS_POPUP | WS_ICONIC, + 0, 0, 0, 0, NULL, NULL, hInst, NULL); + if (hwndDummy == NULL) { + throwIOException(env, "Failed to create window (%d)\n", GetLastError()); + return 0; + } + return (jlong)(intptr_t)hwndDummy; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_DummyWindow_nDestroy(JNIEnv *env, jclass unused, jlong hwnd_address) { + HWND hwndDummy = (HWND)(INT_PTR)hwnd_address; + BOOL result = DestroyWindow(hwndDummy); + if (!result) { + throwIOException(env, "Failed to destroy window (%d)\n", GetLastError()); + } +} diff --git a/src/native/windows/raw/net_java_games_input_RawDevice.c b/src/native/windows/raw/net_java_games_input_RawDevice.c new file mode 100644 index 0000000..5a031c7 --- /dev/null +++ b/src/native/windows/raw/net_java_games_input_RawDevice.c @@ -0,0 +1,70 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#include "rawwinver.h" +#include +#include +#include +#include "net_java_games_input_RawDevice.h" +#include "util.h" + +JNIEXPORT jstring JNICALL Java_net_java_games_input_RawDevice_nGetName(JNIEnv *env, jclass unused, jlong handle_addr) { + HANDLE handle = (HANDLE)(INT_PTR)handle_addr; + UINT res; + UINT name_length; + char *name; + jstring name_str; + + res = GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, NULL, &name_length); + name = (char *)malloc(name_length*sizeof(char)); + res = GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, name, &name_length); + if ((UINT)-1 == res) { + free(name); + throwIOException(env, "Failed to get device name (%d)\n", GetLastError()); + return NULL; + } + name_str = (*env)->NewStringUTF(env, name); + free(name); + return name_str; +} + +static jobject createKeyboardInfo(JNIEnv *env, jobject device_obj, RID_DEVICE_INFO_KEYBOARD *device_info) { + return newJObject(env, "net/java/games/input/RawKeyboardInfo", "(Lnet/java/games/input/RawDevice;IIIIII)V", device_obj, (jint)device_info->dwType, (jint)device_info->dwSubType, (jint)device_info->dwKeyboardMode, (jint)device_info->dwNumberOfFunctionKeys, (jint)device_info->dwNumberOfIndicators, (jint)device_info->dwNumberOfKeysTotal); +} + +static jobject createMouseInfo(JNIEnv *env, jobject device_obj, RID_DEVICE_INFO_MOUSE *device_info) { + return newJObject(env, "net/java/games/input/RawMouseInfo", "(Lnet/java/games/input/RawDevice;III)V", device_obj, (jint)device_info->dwId, (jint)device_info->dwNumberOfButtons, (jint)device_info->dwSampleRate); +} + +static jobject createHIDInfo(JNIEnv *env, jobject device_obj, RID_DEVICE_INFO_HID *device_info) { + return newJObject(env, "net/java/games/input/RawHIDInfo", "(Lnet/java/games/input/RawDevice;IIIII)V", device_obj, (jint)device_info->dwVendorId, (jint)device_info->dwProductId, (jint)device_info->dwVersionNumber, (jint)device_info->usUsagePage, (jint)device_info->usUsage); +} + +JNIEXPORT jobject JNICALL Java_net_java_games_input_RawDevice_nGetInfo(JNIEnv *env, jclass unused, jobject device_obj, jlong handle_addr) { + HANDLE handle = (HANDLE)(INT_PTR)handle_addr; + RID_DEVICE_INFO device_info; + UINT size = sizeof(RID_DEVICE_INFO); + UINT res; + + device_info.cbSize = sizeof(RID_DEVICE_INFO); + res = GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &device_info, &size); + if ((UINT)-1 == res) { + throwIOException(env, "Failed to get device info (%d)\n", GetLastError()); + return NULL; + } + switch (device_info.dwType) { + case RIM_TYPEHID: + return createHIDInfo(env, device_obj,&(device_info.hid)); + case RIM_TYPEKEYBOARD: + return createKeyboardInfo(env, device_obj, &(device_info.keyboard)); + case RIM_TYPEMOUSE: + return createMouseInfo(env, device_obj, &(device_info.mouse)); + default: + throwIOException(env, "Unknown device type: %d\n", device_info.dwType); + return NULL; + } +} diff --git a/src/native/windows/raw/net_java_games_input_RawInputEnvironmentPlugin.c b/src/native/windows/raw/net_java_games_input_RawInputEnvironmentPlugin.c new file mode 100644 index 0000000..9814b7d --- /dev/null +++ b/src/native/windows/raw/net_java_games_input_RawInputEnvironmentPlugin.c @@ -0,0 +1,166 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#include "rawwinver.h" +#include +#include +#include +#include +#include +#include "net_java_games_input_RawInputEnvironmentPlugin.h" +#include "util.h" +#include "winutil.h" + +JNIEXPORT jbyteArray JNICALL Java_net_java_games_input_RawInputEnvironmentPlugin_getKeyboardClassGUID(JNIEnv *env, jclass unused) { + return wrapGUID(env, &GUID_DEVCLASS_KEYBOARD); +} + +JNIEXPORT jbyteArray JNICALL Java_net_java_games_input_RawInputEnvironmentPlugin_getMouseClassGUID(JNIEnv *env, jclass unused) { + return wrapGUID(env, &GUID_DEVCLASS_MOUSE); +} + +JNIEXPORT void JNICALL Java_net_java_games_input_RawInputEnvironmentPlugin_nEnumSetupAPIDevices(JNIEnv *env, jclass unused, jbyteArray guid_array, jobject device_list) { + jclass list_class; + jmethodID add_method; + HDEVINFO hDevInfo; + SP_DEVINFO_DATA DeviceInfoData; + int i; + GUID setup_class_guid; + jstring device_name; + jstring device_instance_id; + jobject setup_api_device; + + list_class = (*env)->GetObjectClass(env, device_list); + if (list_class == NULL) + return; + add_method = (*env)->GetMethodID(env, list_class, "add", "(Ljava/lang/Object;)Z"); + if (add_method == NULL) + return; + unwrapGUID(env, guid_array, &setup_class_guid); + if ((*env)->ExceptionOccurred(env)) + return; + + hDevInfo = SetupDiGetClassDevs(&setup_class_guid, + NULL, + NULL, + DIGCF_PRESENT); + + if (hDevInfo == INVALID_HANDLE_VALUE) { + throwIOException(env, "Failed to create device enumerator (%d)\n", GetLastError()); + return; + } + + + DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData); i++) { + DWORD DataT; + LPTSTR buffer = NULL; + DWORD buffersize = 0; + + + while (!SetupDiGetDeviceRegistryProperty( + hDevInfo, + &DeviceInfoData, + SPDRP_DEVICEDESC, + &DataT, + (PBYTE)buffer, + buffersize, + &buffersize)) { + if (buffer != NULL) + free(buffer); + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + buffer = malloc(buffersize); + } else { + throwIOException(env, "Failed to get device description (%x)\n", GetLastError()); + SetupDiDestroyDeviceInfoList(hDevInfo); + return; + } + } + + device_name = (*env)->NewStringUTF(env, buffer); + if (device_name == NULL) { + free(buffer); + SetupDiDestroyDeviceInfoList(hDevInfo); + return; + } + + while (!SetupDiGetDeviceInstanceId( + hDevInfo, + &DeviceInfoData, + buffer, + buffersize, + &buffersize)) + { + if (buffer != NULL) + free(buffer); + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + buffer = malloc(buffersize); + } else { + throwIOException(env, "Failed to get device instance id (%x)\n", GetLastError()); + SetupDiDestroyDeviceInfoList(hDevInfo); + return; + } + } + + device_instance_id = (*env)->NewStringUTF(env, buffer); + if (buffer != NULL) + free(buffer); + if (device_instance_id == NULL) { + SetupDiDestroyDeviceInfoList(hDevInfo); + return; + } + setup_api_device = newJObject(env, "net/java/games/input/SetupAPIDevice", "(Ljava/lang/String;Ljava/lang/String;)V", device_instance_id, device_name); + if (setup_api_device == NULL) { + SetupDiDestroyDeviceInfoList(hDevInfo); + return; + } + (*env)->CallBooleanMethod(env, device_list, add_method, setup_api_device); + if ((*env)->ExceptionOccurred(env)) { + SetupDiDestroyDeviceInfoList(hDevInfo); + return; + } + } + SetupDiDestroyDeviceInfoList(hDevInfo); +} + +JNIEXPORT void JNICALL Java_net_java_games_input_RawInputEnvironmentPlugin_enumerateDevices(JNIEnv *env, jclass unused, jobject queue, jobject device_list) { + UINT num_devices; + UINT res; + RAWINPUTDEVICELIST *devices; + RAWINPUTDEVICELIST *device; + jobject device_object; + jclass list_class; + jmethodID add_method; + int i; + + list_class = (*env)->GetObjectClass(env, device_list); + if (list_class == NULL) + return; + add_method = (*env)->GetMethodID(env, list_class, "add", "(Ljava/lang/Object;)Z"); + if (add_method == NULL) + return; + + res = GetRawInputDeviceList(NULL, &num_devices, sizeof(RAWINPUTDEVICELIST)); + if ((UINT)-1 == res) { + throwIOException(env, "Failed to get number of devices (%d)\n", GetLastError()); + return; + } + devices = (RAWINPUTDEVICELIST *)malloc(num_devices*sizeof(RAWINPUTDEVICELIST)); + GetRawInputDeviceList(devices, &num_devices, sizeof(RAWINPUTDEVICELIST)); + for (i = 0; i < num_devices; i++) { + device = devices + i; + device_object = newJObject(env, "net/java/games/input/RawDevice", "(Lnet/java/games/input/RawInputEventQueue;JI)V", queue, (jlong)(INT_PTR)device->hDevice, (jint)device->dwType); + if (device_object == NULL) { + free(devices); + return; + } + (*env)->CallBooleanMethod(env, device_list, add_method, device_object); + (*env)->DeleteLocalRef(env, device_object); + } + free(devices); +} + diff --git a/src/native/windows/raw/net_java_games_input_RawInputEventQueue.c b/src/native/windows/raw/net_java_games_input_RawInputEventQueue.c new file mode 100644 index 0000000..d0a1564 --- /dev/null +++ b/src/native/windows/raw/net_java_games_input_RawInputEventQueue.c @@ -0,0 +1,171 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#include "rawwinver.h" +#include +#include +#include "net_java_games_input_RawInputEventQueue.h" +#include "util.h" + +static void handleMouseEvent(JNIEnv *env, jobject self, jmethodID add_method, LONG time, RAWINPUT *data) { + (*env)->CallVoidMethod(env, self, add_method, + (jlong)(INT_PTR)data->header.hDevice, + (jlong)time, + (jint)data->data.mouse.usFlags, + (jint)data->data.mouse.usButtonFlags, + /* + * The Raw Input spec says that the usButtonData + * is a signed value, if RI_MOUSE_WHEEL + * is set in usFlags. However, usButtonData + * is an unsigned value, for unknown reasons, + * and since its only known use is the wheel + * delta, we'll convert it to a signed value here + */ + (jint)(SHORT)data->data.mouse.usButtonData, + (jlong)data->data.mouse.ulRawButtons, + (jlong)data->data.mouse.lLastX, + (jlong)data->data.mouse.lLastY, + (jlong)data->data.mouse.ulExtraInformation + ); +} + +static void handleKeyboardEvent(JNIEnv *env, jobject self, jmethodID add_method, LONG time, RAWINPUT *data) { + (*env)->CallVoidMethod(env, self, add_method, + (jlong)(INT_PTR)data->header.hDevice, + (jlong)time, + (jint)data->data.keyboard.MakeCode, + (jint)data->data.keyboard.Flags, + (jint)data->data.keyboard.VKey, + (jint)data->data.keyboard.Message, + (jlong)data->data.keyboard.ExtraInformation + ); +} + +JNIEXPORT void JNICALL Java_net_java_games_input_RawInputEventQueue_nRegisterDevices(JNIEnv *env, jclass unused, jint flags, jlong hwnd_addr, jobjectArray device_infos) { + BOOL res; + jclass device_info_class; + jmethodID getUsage_method; + jmethodID getUsagePage_method; + RAWINPUTDEVICE *devices; + RAWINPUTDEVICE *device; + jsize num_devices = (*env)->GetArrayLength(env, device_infos); + USHORT usage; + USHORT usage_page; + int i; + HWND hwnd = (HWND)(INT_PTR)hwnd_addr; + +/* res = GetRegisteredRawInputDevices(NULL, &num_devices, sizeof(RAWINPUTDEVICE)); + if (num_devices > 0) { + devices = (RAWINPUTDEVICE *)malloc(num_devices*sizeof(RAWINPUTDEVICE)); + res = GetRegisteredRawInputDevices(devices, &num_devices, sizeof(RAWINPUTDEVICE)); + if (res == -1) { + throwIOException(env, "Failed to get registered raw devices (%d)\n", GetLastError()); + return; + } + for (i = 0; i < num_devices; i++) { + printfJava(env, "from windows: registered: %d %d %p (of %d)\n", devices[i].usUsagePage, devices[i].usUsage, devices[i].hwndTarget, num_devices); + } + free(devices); + }*/ + device_info_class = (*env)->FindClass(env, "net/java/games/input/RawDeviceInfo"); + if (device_info_class == NULL) + return; + getUsage_method = (*env)->GetMethodID(env, device_info_class, "getUsage", "()I"); + if (getUsage_method == NULL) + return; + getUsagePage_method = (*env)->GetMethodID(env, device_info_class, "getUsagePage", "()I"); + if (getUsagePage_method == NULL) + return; + devices = (RAWINPUTDEVICE *)malloc(num_devices*sizeof(RAWINPUTDEVICE)); + if (devices == NULL) { + throwIOException(env, "Failed to allocate device structs\n"); + return; + } + for (i = 0; i < num_devices; i++) { + jobject device_obj = (*env)->GetObjectArrayElement(env, device_infos, i); + if (device_obj == NULL) { + free(devices); + return; + } + usage = (*env)->CallIntMethod(env, device_obj, getUsage_method); + if ((*env)->ExceptionOccurred(env)) { + free(devices); + return; + } + usage_page = (*env)->CallIntMethod(env, device_obj, getUsagePage_method); + if ((*env)->ExceptionOccurred(env)) { + free(devices); + return; + } + device = devices + i; + device->usUsagePage = usage_page; + device->usUsage = usage; + device->dwFlags = flags; + device->hwndTarget = hwnd; + } + res = RegisterRawInputDevices(devices, num_devices, sizeof(RAWINPUTDEVICE)); + free(devices); + if (!res) + throwIOException(env, "Failed to register raw devices (%d)\n", GetLastError()); +} + +JNIEXPORT void JNICALL Java_net_java_games_input_RawInputEventQueue_nPoll(JNIEnv *env, jobject self, jlong hwnd_handle) { + MSG msg; + HWND hwnd = (HWND)(INT_PTR)hwnd_handle; + jmethodID addMouseEvent_method; + jmethodID addKeyboardEvent_method; + UINT input_size; + RAWINPUT *input_data; + LONG time; + jclass self_class = (*env)->GetObjectClass(env, self); + + if (self_class == NULL) + return; + addMouseEvent_method = (*env)->GetMethodID(env, self_class, "addMouseEvent", "(JJIIIJJJJ)V"); + if (addMouseEvent_method == NULL) + return; + addKeyboardEvent_method = (*env)->GetMethodID(env, self_class, "addKeyboardEvent", "(JJIIIIJ)V"); + if (addKeyboardEvent_method == NULL) + return; + if (GetMessage(&msg, hwnd, 0, 0) != 0) { + if (msg.message != WM_INPUT) { + DefWindowProc(hwnd, msg.message, msg.wParam, msg.lParam); + return; // ignore it + } + time = msg.time; + if (GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, NULL, &input_size, sizeof(RAWINPUTHEADER)) == (UINT)-1) { + throwIOException(env, "Failed to get raw input data size (%d)\n", GetLastError()); + DefWindowProc(hwnd, msg.message, msg.wParam, msg.lParam); + return; + } + input_data = (RAWINPUT *)malloc(input_size); + if (input_data == NULL) { + throwIOException(env, "Failed to allocate input data buffer\n"); + DefWindowProc(hwnd, msg.message, msg.wParam, msg.lParam); + return; + } + if (GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, input_data, &input_size, sizeof(RAWINPUTHEADER)) == (UINT)-1) { + free(input_data); + throwIOException(env, "Failed to get raw input data (%d)\n", GetLastError()); + DefWindowProc(hwnd, msg.message, msg.wParam, msg.lParam); + return; + } + switch (input_data->header.dwType) { + case RIM_TYPEMOUSE: + handleMouseEvent(env, self, addMouseEvent_method, time, input_data); + break; + case RIM_TYPEKEYBOARD: + handleKeyboardEvent(env, self, addKeyboardEvent_method, time, input_data); + break; + default: + /* ignore other types of message */ + break; + } + free(input_data); + DefWindowProc(hwnd, msg.message, msg.wParam, msg.lParam); + } +} diff --git a/src/native/windows/raw/rawwinver.h b/src/native/windows/raw/rawwinver.h new file mode 100644 index 0000000..882341a --- /dev/null +++ b/src/native/windows/raw/rawwinver.h @@ -0,0 +1,14 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#ifndef RAWWINVER_H +#define RAWWINVER_H + +#define _WIN32_WINNT 0x0501 +#define WINVER 0x0501 + +#endif diff --git a/src/native/windows/winutil.c b/src/native/windows/winutil.c new file mode 100644 index 0000000..93606dd --- /dev/null +++ b/src/native/windows/winutil.c @@ -0,0 +1,21 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#include "winutil.h" + +jbyteArray wrapGUID(JNIEnv *env, const GUID *guid) { + jbyteArray guid_array = (*env)->NewByteArray(env, sizeof(GUID)); + if (guid_array == NULL) + return NULL; + (*env)->SetByteArrayRegion(env, guid_array, 0, sizeof(GUID), (jbyte *)guid); + return guid_array; +} + +void unwrapGUID(JNIEnv *env, const jobjectArray byte_array, GUID *guid) { + (*env)->GetByteArrayRegion(env, byte_array, 0, sizeof(GUID), (jbyte *)guid); +} + diff --git a/src/native/windows/winutil.h b/src/native/windows/winutil.h new file mode 100644 index 0000000..169280e --- /dev/null +++ b/src/native/windows/winutil.h @@ -0,0 +1,17 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +#ifndef _WINUTIL_H +#define _WINUTIL_H + +#include +#include + +extern jbyteArray wrapGUID(JNIEnv *env, const GUID *guid); +extern void unwrapGUID(JNIEnv *env, const jobjectArray byte_array, GUID *guid); + +#endif diff --git a/src/native/wintab/WinTabUtils.c b/src/native/wintab/WinTabUtils.c new file mode 100644 index 0000000..3c9d709 --- /dev/null +++ b/src/native/wintab/WinTabUtils.c @@ -0,0 +1,167 @@ +/*---------------------------------------------------------------------------- + + NAME + Utils.c + + PURPOSE + Some general-purpose functions for the WinTab demos. + + COPYRIGHT + Copyright (c) Wacom Company, Ltd. 2014 All Rights Reserved + All rights reserved. + + The text and information contained in this file may be freely used, + copied, or distributed without compensation or licensing restrictions. + +---------------------------------------------------------------------------- */ + +#include "WinTabUtils.h" + +#ifdef WACOM_DEBUG + +void WacomTrace( char *lpszFormat, ...); + +#define WACOM_ASSERT( x ) assert( x ) +#define WACOM_TRACE(...) WacomTrace(__VA_ARGS__) +#else +#define WACOM_TRACE(...) +#define WACOM_ASSERT( x ) + +#endif // WACOM_DEBUG + +////////////////////////////////////////////////////////////////////////////// +HINSTANCE ghWintab = NULL; + +WTINFOA gpWTInfoA = NULL; +WTOPENA gpWTOpenA = NULL; +WTGETA gpWTGetA = NULL; +WTSETA gpWTSetA = NULL; +WTCLOSE gpWTClose = NULL; +WTPACKET gpWTPacket = NULL; +WTENABLE gpWTEnable = NULL; +WTOVERLAP gpWTOverlap = NULL; +WTSAVE gpWTSave = NULL; +WTCONFIG gpWTConfig = NULL; +WTRESTORE gpWTRestore = NULL; +WTEXTSET gpWTExtSet = NULL; +WTEXTGET gpWTExtGet = NULL; +WTQUEUESIZESET gpWTQueueSizeSet = NULL; +WTDATAPEEK gpWTDataPeek = NULL; +WTPACKETSGET gpWTPacketsGet = NULL; + +// TODO - add more wintab32 function pointers as needed + +#define GETPROCADDRESS(type, func) \ + gp##func = (type)GetProcAddress(ghWintab, #func); \ + if (!gp##func){ WACOM_ASSERT(FALSE); UnloadWintab(); return FALSE; } + +////////////////////////////////////////////////////////////////////////////// +// Purpose +// Find wintab32.dll and load it. +// Find the exported functions we need from it. +// +// Returns +// TRUE on success. +// FALSE on failure. +// +BOOL LoadWintab( void ) +{ + ghWintab = LoadLibraryA( "Wintab32.dll" ); + if ( !ghWintab ) + { + DWORD err = GetLastError(); + WACOM_TRACE("LoadLibrary error: %i\n", err); + return FALSE; + } + + // Explicitly find the exported Wintab functions in which we are interested. + // We are using the ASCII, not unicode versions (where applicable). + GETPROCADDRESS( WTOPENA, WTOpenA ); + GETPROCADDRESS( WTINFOA, WTInfoA ); + GETPROCADDRESS( WTGETA, WTGetA ); + GETPROCADDRESS( WTSETA, WTSetA ); + GETPROCADDRESS( WTPACKET, WTPacket ); + GETPROCADDRESS( WTCLOSE, WTClose ); + GETPROCADDRESS( WTENABLE, WTEnable ); + GETPROCADDRESS( WTOVERLAP, WTOverlap ); + GETPROCADDRESS( WTSAVE, WTSave ); + GETPROCADDRESS( WTCONFIG, WTConfig ); + GETPROCADDRESS( WTRESTORE, WTRestore ); + GETPROCADDRESS( WTEXTSET, WTExtSet ); + GETPROCADDRESS( WTEXTGET, WTExtGet ); + GETPROCADDRESS( WTQUEUESIZESET, WTQueueSizeSet ); + GETPROCADDRESS( WTDATAPEEK, WTDataPeek ); + GETPROCADDRESS( WTPACKETSGET, WTPacketsGet ); + + + // TODO - don't forget to NULL out pointers in UnloadWintab(). + return TRUE; +} + + + +////////////////////////////////////////////////////////////////////////////// +// Purpose +// Uninitializes use of wintab32.dll +// +// Returns +// Nothing. +// +void UnloadWintab( void ) +{ + WACOM_TRACE( "UnloadWintab()\n" ); + + if ( ghWintab ) + { + FreeLibrary( ghWintab ); + ghWintab = NULL; + } + + gpWTOpenA = NULL; + gpWTClose = NULL; + gpWTInfoA = NULL; + gpWTPacket = NULL; + gpWTEnable = NULL; + gpWTOverlap = NULL; + gpWTSave = NULL; + gpWTConfig = NULL; + gpWTGetA = NULL; + gpWTSetA = NULL; + gpWTRestore = NULL; + gpWTExtSet = NULL; + gpWTExtGet = NULL; + gpWTQueueSizeSet = NULL; + gpWTDataPeek = NULL; + gpWTPacketsGet = NULL; +} + + + +#ifdef WACOM_DEBUG + +////////////////////////////////////////////////////////////////////////////// + +void WacomTrace( char *lpszFormat, ...) +{ + char szTraceMessage[ 128 ]; + + int nBytesWritten; + + va_list args; + + WACOM_ASSERT( lpszFormat ); + + va_start( args, lpszFormat ); + + nBytesWritten = _vsnprintf( szTraceMessage, sizeof( szTraceMessage ) - 1, + lpszFormat, args ); + + if ( nBytesWritten > 0 ) + { + OutputDebugStringA( szTraceMessage ); + } + + va_end( args ); +} + +#endif // WACOM_DEBUG diff --git a/src/native/wintab/WinTabUtils.h b/src/native/wintab/WinTabUtils.h new file mode 100644 index 0000000..89b31a3 --- /dev/null +++ b/src/native/wintab/WinTabUtils.h @@ -0,0 +1,92 @@ +/*---------------------------------------------------------------------------- + + NAME + Utils.h + + PURPOSE + Defines for the general-purpose functions for the WinTab demos. + + COPYRIGHT + Copyright (c) Wacom Company, Ltd. 2014 All Rights Reserved + All rights reserved. + + The text and information contained in this file may be freely used, + copied, or distributed without compensation or licensing restrictions. + +---------------------------------------------------------------------------- */ +#pragma once + +#include +#include +#include +#include + +#include "wintab.h" // NOTE: get from wactab header package + + +////////////////////////////////////////////////////////////////////////////// +#define WACOM_DEBUG + +// Ignore warnings about using unsafe string functions. +#pragma warning( disable : 4996 ) + +////////////////////////////////////////////////////////////////////////////// +// Function pointers to Wintab functions exported from wintab32.dll. +typedef UINT ( API * WTINFOA ) ( UINT, UINT, LPVOID ); +typedef HCTX ( API * WTOPENA )( HWND, LPLOGCONTEXTA, BOOL ); +typedef BOOL ( API * WTGETA ) ( HCTX, LPLOGCONTEXT ); +typedef BOOL ( API * WTSETA ) ( HCTX, LPLOGCONTEXT ); +typedef BOOL ( API * WTCLOSE ) ( HCTX ); +typedef BOOL ( API * WTENABLE ) ( HCTX, BOOL ); +typedef BOOL ( API * WTPACKET ) ( HCTX, UINT, LPVOID ); +typedef BOOL ( API * WTOVERLAP ) ( HCTX, BOOL ); +typedef BOOL ( API * WTSAVE ) ( HCTX, LPVOID ); +typedef BOOL ( API * WTCONFIG ) ( HCTX, HWND ); +typedef HCTX ( API * WTRESTORE ) ( HWND, LPVOID, BOOL ); +typedef BOOL ( API * WTEXTSET ) ( HCTX, UINT, LPVOID ); +typedef BOOL ( API * WTEXTGET ) ( HCTX, UINT, LPVOID ); +typedef BOOL ( API * WTQUEUESIZESET ) ( HCTX, int ); +typedef int ( API * WTDATAPEEK ) ( HCTX, UINT, UINT, int, LPVOID, LPINT); +typedef int ( API * WTPACKETSGET ) (HCTX, int, LPVOID); + +// TODO - add more wintab32 function defs as needed + +// Loaded Wintab32 API functions. +extern HINSTANCE ghWintab; + +extern WTINFOA gpWTInfoA; +extern WTOPENA gpWTOpenA; +extern WTGETA gpWTGetA; +extern WTSETA gpWTSetA; +extern WTCLOSE gpWTClose; +extern WTPACKET gpWTPacket; +extern WTENABLE gpWTEnable; +extern WTOVERLAP gpWTOverlap; +extern WTSAVE gpWTSave; +extern WTCONFIG gpWTConfig; +extern WTRESTORE gpWTRestore; +extern WTEXTSET gpWTExtSet; +extern WTEXTGET gpWTExtGet; +extern WTQUEUESIZESET gpWTQueueSizeSet; +extern WTDATAPEEK gpWTDataPeek; +extern WTPACKETSGET gpWTPacketsGet; + +// TODO - add more wintab32 function pointers as needed + +////////////////////////////////////////////////////////////////////////////// +BOOL LoadWintab( void ); +void UnloadWintab( void ); + +////////////////////////////////////////////////////////////////////////////// +#ifdef WACOM_DEBUG + +void WacomTrace( char *lpszFormat, ...); + +#define WACOM_ASSERT( x ) assert( x ) +#define WACOM_TRACE(...) WacomTrace(__VA_ARGS__) +#else +#define WACOM_TRACE(...) +#define WACOM_ASSERT( x ) + +#endif // WACOM_DEBUG + diff --git a/src/native/wintab/build.xml b/src/native/wintab/build.xml new file mode 100644 index 0000000..8de51f9 --- /dev/null +++ b/src/native/wintab/build.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/native/wintab/net_java_games_input_WinTabContext.c b/src/native/wintab/net_java_games_input_WinTabContext.c new file mode 100644 index 0000000..d455197 --- /dev/null +++ b/src/native/wintab/net_java_games_input_WinTabContext.c @@ -0,0 +1,106 @@ +#include +#include + +#include +#include "net_java_games_input_WinTabContext.h" +#include "wintabutils.h" +#include +//#define PACKETDATA ( PK_X | PK_Y | PK_Z | PK_BUTTONS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ROTATION | PK_ORIENTATION | PK_CURSOR ) +#define PACKETDATA ( PK_TIME | PK_X | PK_Y | PK_Z | PK_BUTTONS | PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION | PK_CURSOR ) +#define PACKETMODE 0 +#include +#include + +#define MAX_PACKETS 20 + +JNIEXPORT jlong JNICALL Java_net_java_games_input_WinTabContext_nOpen(JNIEnv *env, jclass unused, jlong hWnd_long) { + LOGCONTEXT context; + HWND hWnd = (HWND)(INT_PTR)hWnd_long; + HCTX hCtx = NULL; + + jclass booleanClass = (*env)->FindClass(env, "java/lang/Boolean"); + jstring propertyName = (*env)->NewStringUTF(env, "jinput.wintab.detachcursor"); + jmethodID getBooleanMethod = (*env)->GetStaticMethodID(env, booleanClass, "getBoolean", "(Ljava/lang/String;)Z"); + jboolean detachCursor = (*env)->CallStaticBooleanMethod(env, booleanClass, getBooleanMethod, propertyName); + + if(!LoadWintab()) { + throwIOException(env, "Failed to load wintab (%d)\n", GetLastError()); + } else { + printfJava(env, "Wintab dll loaded\n"); + } + + if (!gpWTInfoA(0, 0, NULL)) { + throwIOException(env, "Wintab is not available (%d)\n", GetLastError()); + } else { + printfJava(env, "Wintab is available\n"); + } + + gpWTInfoA(WTI_DEFCONTEXT, 0, &context); + + wsprintf(context.lcName, "JInput Digitizing"); + context.lcPktData = PACKETDATA; + context.lcPktMode = PACKETMODE; + context.lcMoveMask = PACKETDATA; + context.lcBtnUpMask = context.lcBtnDnMask; + if(!detachCursor) { + context.lcOptions |= CXO_SYSTEM; + } + + /* open the region */ + hCtx = gpWTOpenA(hWnd, &context, TRUE); + + return (jlong)(intptr_t)hCtx; +} + +JNIEXPORT void JNICALL Java_net_java_games_input_WinTabContext_nClose(JNIEnv *env, jclass unused, jlong hCtx_long) { + gpWTClose((HCTX)(INT_PTR)hCtx_long); + UnloadWintab(); +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_WinTabContext_nGetNumberOfSupportedDevices(JNIEnv *env, jclass unused) { + int numDevices; + gpWTInfoA(WTI_INTERFACE, IFC_NDEVICES, &numDevices); + return numDevices; +} + +JNIEXPORT jobjectArray JNICALL Java_net_java_games_input_WinTabContext_nGetPackets(JNIEnv *env, jclass unused, jlong hCtx_long) { + + jobjectArray retval; + int i=0; + PACKET packets[MAX_PACKETS]; + int numberRead = gpWTPacketsGet((HCTX)(INT_PTR)hCtx_long, MAX_PACKETS, packets); + jclass winTabPacketClass = (*env)->FindClass(env, "net/java/games/input/WinTabPacket"); + jfieldID packetTimeField = (*env)->GetFieldID(env, winTabPacketClass, "PK_TIME", "J"); + jfieldID packetXAxisField = (*env)->GetFieldID(env, winTabPacketClass, "PK_X", "I"); + jfieldID packetYAxisField = (*env)->GetFieldID(env, winTabPacketClass, "PK_Y", "I"); + jfieldID packetZAxisField = (*env)->GetFieldID(env, winTabPacketClass, "PK_Z", "I"); + jfieldID packetButtonsField = (*env)->GetFieldID(env, winTabPacketClass, "PK_BUTTONS", "I"); + jfieldID packetNPressureAxisField = (*env)->GetFieldID(env, winTabPacketClass, "PK_NORMAL_PRESSURE", "I"); + jfieldID packetTPressureAxisField = (*env)->GetFieldID(env, winTabPacketClass, "PK_TANGENT_PRESSURE", "I"); + jfieldID packetCursorField = (*env)->GetFieldID(env, winTabPacketClass, "PK_CURSOR", "I"); + jfieldID packetOrientationAltAxisField = (*env)->GetFieldID(env, winTabPacketClass, "PK_ORIENTATION_ALT", "I"); + jfieldID packetOrientationAzAxisField = (*env)->GetFieldID(env, winTabPacketClass, "PK_ORIENTATION_AZ", "I"); + jfieldID packetOrientationTwistAxisField = (*env)->GetFieldID(env, winTabPacketClass, "PK_ORIENTATION_TWIST", "I"); + jobject prototypePacket = newJObject(env, "net/java/games/input/WinTabPacket", "()V"); + + retval = (*env)->NewObjectArray(env, numberRead, winTabPacketClass, NULL); + for(i=0;iSetLongField(env, tempPacket, packetTimeField, packets[i].pkTime); + (*env)->SetIntField(env, tempPacket, packetXAxisField, packets[i].pkX); + (*env)->SetIntField(env, tempPacket, packetYAxisField, packets[i].pkY); + (*env)->SetIntField(env, tempPacket, packetZAxisField, packets[i].pkZ); + (*env)->SetIntField(env, tempPacket, packetButtonsField, packets[i].pkButtons); + (*env)->SetIntField(env, tempPacket, packetNPressureAxisField, packets[i].pkNormalPressure); + (*env)->SetIntField(env, tempPacket, packetTPressureAxisField, packets[i].pkTangentPressure); + (*env)->SetIntField(env, tempPacket, packetCursorField, packets[i].pkCursor); + (*env)->SetIntField(env, tempPacket, packetOrientationAltAxisField, packets[i].pkOrientation.orAzimuth); + (*env)->SetIntField(env, tempPacket, packetOrientationAzAxisField, packets[i].pkOrientation.orAltitude); + (*env)->SetIntField(env, tempPacket, packetOrientationTwistAxisField, packets[i].pkOrientation.orTwist); + + (*env)->SetObjectArrayElement(env, retval, i, tempPacket); + } + + return retval; +} diff --git a/src/native/wintab/net_java_games_input_WinTabDevice.c b/src/native/wintab/net_java_games_input_WinTabDevice.c new file mode 100644 index 0000000..45af0c1 --- /dev/null +++ b/src/native/wintab/net_java_games_input_WinTabDevice.c @@ -0,0 +1,107 @@ +#include +#include + +#include +#include "net_java_games_input_WinTabDevice.h" +#include "util.h" +#include "wintabutils.h" +#include +#include +#define PACKETDATA ( PK_X | PK_Y | PK_Z | PK_BUTTONS | PK_NORMAL_PRESSURE | PK_ORIENTATION | PK_CURSOR ) +#define PACKETMODE 0 +#include + +JNIEXPORT jstring JNICALL Java_net_java_games_input_WinTabDevice_nGetName(JNIEnv *env, jclass unused, jint deviceIndex) { + char name[50]; + gpWTInfoA(WTI_DEVICES + deviceIndex, DVC_NAME, name); + return (*env)->NewStringUTF(env, name); +} + +JNIEXPORT jintArray JNICALL Java_net_java_games_input_WinTabDevice_nGetAxisDetails(JNIEnv *env, jclass unused, jint deviceIndex, jint axisId) { + UINT type; + AXIS threeAxisArray[3]; + AXIS axis; + long threeAxisData[6]; + long axisData[2]; + int res; + jintArray retVal = NULL; + + if(axisId==net_java_games_input_WinTabDevice_XAxis) type = DVC_X; + else if(axisId==net_java_games_input_WinTabDevice_YAxis) type = DVC_Y; + else if(axisId==net_java_games_input_WinTabDevice_ZAxis) type = DVC_Z; + else if(axisId==net_java_games_input_WinTabDevice_NPressureAxis) type = DVC_NPRESSURE; + else if(axisId==net_java_games_input_WinTabDevice_TPressureAxis) type = DVC_TPRESSURE; + else if(axisId==net_java_games_input_WinTabDevice_OrientationAxis) type = DVC_ORIENTATION; + else if(axisId==net_java_games_input_WinTabDevice_RotationAxis) type = DVC_ROTATION; + + if(axisId==net_java_games_input_WinTabDevice_RotationAxis || axisId==net_java_games_input_WinTabDevice_OrientationAxis) { + res = gpWTInfoA(WTI_DEVICES + deviceIndex, type, &threeAxisArray); + if(res!=0) { + threeAxisData[0] = threeAxisArray[0].axMin; + threeAxisData[1] = threeAxisArray[0].axMax; + threeAxisData[2] = threeAxisArray[1].axMin; + threeAxisData[3] = threeAxisArray[1].axMax; + threeAxisData[4] = threeAxisArray[2].axMin; + threeAxisData[5] = threeAxisArray[2].axMax; + retVal = (*env)->NewIntArray(env, 6); + (*env)->SetIntArrayRegion(env, retVal, 0, 6, threeAxisData); + } + } else { + res = gpWTInfoA(WTI_DEVICES + deviceIndex, type, &axis); + if(res!=0) { + axisData[0] = axis.axMin; + axisData[1] = axis.axMax; + retVal = (*env)->NewIntArray(env, 2); + (*env)->SetIntArrayRegion(env, retVal, 0, 2, axisData); + } + } + + if(retVal==NULL) { + retVal = (*env)->NewIntArray(env, 0); + } + + return retVal; +} + +JNIEXPORT jobjectArray JNICALL Java_net_java_games_input_WinTabDevice_nGetCursorNames(JNIEnv *env, jclass unused, jint deviceId) { + int numberCursorTypes; + int firstCursorType; + char name[50]; + int i; + jclass stringClass = (*env)->FindClass(env, "java/lang/String"); + jstring nameString; + jobjectArray retval; + + gpWTInfoA(WTI_DEVICES + deviceId, DVC_NCSRTYPES, &numberCursorTypes); + gpWTInfoA(WTI_DEVICES + deviceId, DVC_FIRSTCSR, &firstCursorType); + + retval = (*env)->NewObjectArray(env, numberCursorTypes, stringClass, NULL); + + for(i=0;iNewStringUTF(env, name); + (*env)->SetObjectArrayElement(env, retval, i-firstCursorType, nameString); + } + + return retval; +} + +JNIEXPORT jint JNICALL Java_net_java_games_input_WinTabDevice_nGetMaxButtonCount(JNIEnv *env, jclass unused, jint deviceId) { + int numberCursorTypes; + int firstCursorType; + byte buttonCount; + int i; + byte retval=0; + + gpWTInfoA(WTI_DEVICES + deviceId, DVC_NCSRTYPES, &numberCursorTypes); + gpWTInfoA(WTI_DEVICES + deviceId, DVC_FIRSTCSR, &firstCursorType); + + for(i=0;iretval) { + retval = buttonCount; + } + } + + return (jint)retval; +} diff --git a/src/overview.html b/src/overview.html new file mode 100644 index 0000000..9290060 --- /dev/null +++ b/src/overview.html @@ -0,0 +1,13 @@ + + + + + + + + This is a fork of the jinput project. The fork is at https://github.com/hervegirod/jinput2. +
+
+ The original jinput project is at https://github.com/jinput/jinput + + diff --git a/src/plugins/awt/net/java/games/input/AWTEnvironmentPlugin.java b/src/plugins/awt/net/java/games/input/AWTEnvironmentPlugin.java new file mode 100644 index 0000000..b6b062a --- /dev/null +++ b/src/plugins/awt/net/java/games/input/AWTEnvironmentPlugin.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2004 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import net.java.games.input.Controller; +import net.java.games.input.ControllerEnvironment; +import net.java.games.util.plugins.Plugin; + +/** + * @author Jeremy + * @author elias + */ +public class AWTEnvironmentPlugin extends ControllerEnvironment implements Plugin { + + private final Controller[] controllers; + + public AWTEnvironmentPlugin() { + this.controllers = new Controller[]{new AWTKeyboard(), new AWTMouse()}; + } + + public Controller[] getControllers() { + return controllers; + } + + public boolean isSupported() { + return true; + } +} diff --git a/src/plugins/awt/net/java/games/input/AWTKeyMap.java b/src/plugins/awt/net/java/games/input/AWTKeyMap.java new file mode 100644 index 0000000..cb0c548 --- /dev/null +++ b/src/plugins/awt/net/java/games/input/AWTKeyMap.java @@ -0,0 +1,289 @@ +/** + * Copyright (C) 2004 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.awt.event.KeyEvent; + +/** + * @author Jeremy + * @author elias + */ +final class AWTKeyMap { + public final static Component.Identifier.Key mapKeyCode(int key_code) { + switch (key_code) { + case KeyEvent.VK_0: + return Component.Identifier.Key._0; + case KeyEvent.VK_1: + return Component.Identifier.Key._1; + case KeyEvent.VK_2: + return Component.Identifier.Key._2; + case KeyEvent.VK_3: + return Component.Identifier.Key._3; + case KeyEvent.VK_4: + return Component.Identifier.Key._4; + case KeyEvent.VK_5: + return Component.Identifier.Key._5; + case KeyEvent.VK_6: + return Component.Identifier.Key._6; + case KeyEvent.VK_7: + return Component.Identifier.Key._7; + case KeyEvent.VK_8: + return Component.Identifier.Key._8; + case KeyEvent.VK_9: + return Component.Identifier.Key._9; + + case KeyEvent.VK_Q: + return Component.Identifier.Key.Q; + case KeyEvent.VK_W: + return Component.Identifier.Key.W; + case KeyEvent.VK_E: + return Component.Identifier.Key.E; + case KeyEvent.VK_R: + return Component.Identifier.Key.R; + case KeyEvent.VK_T: + return Component.Identifier.Key.T; + case KeyEvent.VK_Y: + return Component.Identifier.Key.Y; + case KeyEvent.VK_U: + return Component.Identifier.Key.U; + case KeyEvent.VK_I: + return Component.Identifier.Key.I; + case KeyEvent.VK_O: + return Component.Identifier.Key.O; + case KeyEvent.VK_P: + return Component.Identifier.Key.P; + case KeyEvent.VK_A: + return Component.Identifier.Key.A; + case KeyEvent.VK_S: + return Component.Identifier.Key.S; + case KeyEvent.VK_D: + return Component.Identifier.Key.D; + case KeyEvent.VK_F: + return Component.Identifier.Key.F; + case KeyEvent.VK_G: + return Component.Identifier.Key.G; + case KeyEvent.VK_H: + return Component.Identifier.Key.H; + case KeyEvent.VK_J: + return Component.Identifier.Key.J; + case KeyEvent.VK_K: + return Component.Identifier.Key.K; + case KeyEvent.VK_L: + return Component.Identifier.Key.L; + case KeyEvent.VK_Z: + return Component.Identifier.Key.Z; + case KeyEvent.VK_X: + return Component.Identifier.Key.X; + case KeyEvent.VK_C: + return Component.Identifier.Key.C; + case KeyEvent.VK_V: + return Component.Identifier.Key.V; + case KeyEvent.VK_B: + return Component.Identifier.Key.B; + case KeyEvent.VK_N: + return Component.Identifier.Key.N; + case KeyEvent.VK_M: + return Component.Identifier.Key.M; + + case KeyEvent.VK_F1: + return Component.Identifier.Key.F1; + case KeyEvent.VK_F2: + return Component.Identifier.Key.F2; + case KeyEvent.VK_F3: + return Component.Identifier.Key.F3; + case KeyEvent.VK_F4: + return Component.Identifier.Key.F4; + case KeyEvent.VK_F5: + return Component.Identifier.Key.F5; + case KeyEvent.VK_F6: + return Component.Identifier.Key.F6; + case KeyEvent.VK_F7: + return Component.Identifier.Key.F7; + case KeyEvent.VK_F8: + return Component.Identifier.Key.F8; + case KeyEvent.VK_F9: + return Component.Identifier.Key.F9; + case KeyEvent.VK_F10: + return Component.Identifier.Key.F10; + case KeyEvent.VK_F11: + return Component.Identifier.Key.F11; + case KeyEvent.VK_F12: + return Component.Identifier.Key.F12; + + case KeyEvent.VK_ESCAPE: + return Component.Identifier.Key.ESCAPE; + case KeyEvent.VK_MINUS: + return Component.Identifier.Key.MINUS; + case KeyEvent.VK_EQUALS: + return Component.Identifier.Key.EQUALS; + case KeyEvent.VK_BACK_SPACE: + return Component.Identifier.Key.BACKSLASH; + case KeyEvent.VK_TAB: + return Component.Identifier.Key.TAB; + case KeyEvent.VK_OPEN_BRACKET: + return Component.Identifier.Key.LBRACKET; + case KeyEvent.VK_CLOSE_BRACKET: + return Component.Identifier.Key.RBRACKET; + case KeyEvent.VK_SEMICOLON: + return Component.Identifier.Key.SEMICOLON; + case KeyEvent.VK_QUOTE: + return Component.Identifier.Key.APOSTROPHE; + case KeyEvent.VK_NUMBER_SIGN: + return Component.Identifier.Key.GRAVE; + case KeyEvent.VK_BACK_SLASH: + return Component.Identifier.Key.BACKSLASH; + case KeyEvent.VK_PERIOD: + return Component.Identifier.Key.PERIOD; + case KeyEvent.VK_SLASH: + return Component.Identifier.Key.SLASH; + case KeyEvent.VK_MULTIPLY: + return Component.Identifier.Key.MULTIPLY; + case KeyEvent.VK_SPACE: + return Component.Identifier.Key.SPACE; + case KeyEvent.VK_CAPS_LOCK: + return Component.Identifier.Key.CAPITAL; + case KeyEvent.VK_NUM_LOCK: + return Component.Identifier.Key.NUMLOCK; + case KeyEvent.VK_SCROLL_LOCK: + return Component.Identifier.Key.SCROLL; + case KeyEvent.VK_NUMPAD7: + return Component.Identifier.Key.NUMPAD7; + case KeyEvent.VK_NUMPAD8: + return Component.Identifier.Key.NUMPAD8; + case KeyEvent.VK_NUMPAD9: + return Component.Identifier.Key.NUMPAD9; + case KeyEvent.VK_SUBTRACT: + return Component.Identifier.Key.SUBTRACT; + case KeyEvent.VK_NUMPAD4: + return Component.Identifier.Key.NUMPAD4; + case KeyEvent.VK_NUMPAD5: + return Component.Identifier.Key.NUMPAD5; + case KeyEvent.VK_NUMPAD6: + return Component.Identifier.Key.NUMPAD6; + case KeyEvent.VK_ADD: + return Component.Identifier.Key.ADD; + case KeyEvent.VK_NUMPAD1: + return Component.Identifier.Key.NUMPAD1; + case KeyEvent.VK_NUMPAD2: + return Component.Identifier.Key.NUMPAD2; + case KeyEvent.VK_NUMPAD3: + return Component.Identifier.Key.NUMPAD3; + case KeyEvent.VK_NUMPAD0: + return Component.Identifier.Key.NUMPAD0; + case KeyEvent.VK_DECIMAL: + return Component.Identifier.Key.DECIMAL; + + case KeyEvent.VK_KANA: + return Component.Identifier.Key.KANA; + case KeyEvent.VK_CONVERT: + return Component.Identifier.Key.CONVERT; + case KeyEvent.VK_NONCONVERT: + return Component.Identifier.Key.NOCONVERT; + + case KeyEvent.VK_CIRCUMFLEX: + return Component.Identifier.Key.CIRCUMFLEX; + case KeyEvent.VK_AT: + return Component.Identifier.Key.AT; + case KeyEvent.VK_COLON: + return Component.Identifier.Key.COLON; + case KeyEvent.VK_UNDERSCORE: + return Component.Identifier.Key.UNDERLINE; + case KeyEvent.VK_KANJI: + return Component.Identifier.Key.KANJI; + + case KeyEvent.VK_STOP: + return Component.Identifier.Key.STOP; + + case KeyEvent.VK_DIVIDE: + return Component.Identifier.Key.DIVIDE; + + case KeyEvent.VK_PAUSE: + return Component.Identifier.Key.PAUSE; + case KeyEvent.VK_HOME: + return Component.Identifier.Key.HOME; + case KeyEvent.VK_UP: + return Component.Identifier.Key.UP; + case KeyEvent.VK_PAGE_UP: + return Component.Identifier.Key.PAGEUP; + case KeyEvent.VK_LEFT: + return Component.Identifier.Key.LEFT; + case KeyEvent.VK_RIGHT: + return Component.Identifier.Key.RIGHT; + case KeyEvent.VK_END: + return Component.Identifier.Key.END; + case KeyEvent.VK_DOWN: + return Component.Identifier.Key.DOWN; + case KeyEvent.VK_PAGE_DOWN: + return Component.Identifier.Key.PAGEDOWN; + case KeyEvent.VK_INSERT: + return Component.Identifier.Key.INSERT; + case KeyEvent.VK_DELETE: + return Component.Identifier.Key.DELETE; + default: + return Component.Identifier.Key.UNKNOWN; + } + } + + public final static Component.Identifier.Key map(KeyEvent event) { + int key_code = event.getKeyCode(); + int key_location = event.getKeyLocation(); + switch (key_code) { + case KeyEvent.VK_CONTROL: + if (key_location == KeyEvent.KEY_LOCATION_RIGHT) + return Component.Identifier.Key.RCONTROL; + else + return Component.Identifier.Key.LCONTROL; + case KeyEvent.VK_SHIFT: + if (key_location == KeyEvent.KEY_LOCATION_RIGHT) + return Component.Identifier.Key.RSHIFT; + else + return Component.Identifier.Key.LSHIFT; + case KeyEvent.VK_ALT: + if (key_location == KeyEvent.KEY_LOCATION_RIGHT) + return Component.Identifier.Key.RALT; + else + return Component.Identifier.Key.LALT; + //this is 1.5 only +/* case KeyEvent.VK_WINDOWS: + if (key_location == KeyEvent.KEY_LOCATION_RIGHT) + return Component.Identifier.Key.RWIN; + else + return Component.Identifier.Key.LWIN;*/ + case KeyEvent.VK_ENTER: + if (key_location == KeyEvent.KEY_LOCATION_NUMPAD) + return Component.Identifier.Key.NUMPADENTER; + else + return Component.Identifier.Key.RETURN; + case KeyEvent.VK_COMMA: + if (key_location == KeyEvent.KEY_LOCATION_NUMPAD) + return Component.Identifier.Key.NUMPADCOMMA; + else + return Component.Identifier.Key.COMMA; + default: + return mapKeyCode(key_code); + } + } +} diff --git a/src/plugins/awt/net/java/games/input/AWTKeyboard.java b/src/plugins/awt/net/java/games/input/AWTKeyboard.java new file mode 100644 index 0000000..b30a39d --- /dev/null +++ b/src/plugins/awt/net/java/games/input/AWTKeyboard.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2004 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.awt.AWTEvent; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; +import java.awt.event.KeyEvent; + +import java.util.List; +import java.util.ArrayList; + +import java.io.IOException; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * @author Jeremy + * @author elias + */ +final class AWTKeyboard extends Keyboard implements AWTEventListener { + private final List awt_events = new ArrayList<>(); + private Event[] processed_events; + private int processed_events_index; + + protected AWTKeyboard() { + super("AWTKeyboard", createComponents(), new Controller[]{}, new Rumbler[]{}); + Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK); + resizeEventQueue(EVENT_QUEUE_DEPTH); + } + + private final static Component[] createComponents() { + List components = new ArrayList<>(); + Field[] vkey_fields = KeyEvent.class.getFields(); + for (int i = 0; i < vkey_fields.length; i++) { + Field vkey_field = vkey_fields[i]; + try { + if (Modifier.isStatic(vkey_field.getModifiers()) && vkey_field.getType() == int.class && + vkey_field.getName().startsWith("VK_")) { + int vkey_code = vkey_field.getInt(null); + Component.Identifier.Key key_id = AWTKeyMap.mapKeyCode(vkey_code); + if (key_id != Component.Identifier.Key.UNKNOWN) + components.add(new Key(key_id)); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + components.add(new Key(Component.Identifier.Key.RCONTROL)); + components.add(new Key(Component.Identifier.Key.LCONTROL)); + components.add(new Key(Component.Identifier.Key.RSHIFT)); + components.add(new Key(Component.Identifier.Key.LSHIFT)); + components.add(new Key(Component.Identifier.Key.RALT)); + components.add(new Key(Component.Identifier.Key.LALT)); + components.add(new Key(Component.Identifier.Key.NUMPADENTER)); + components.add(new Key(Component.Identifier.Key.RETURN)); + components.add(new Key(Component.Identifier.Key.NUMPADCOMMA)); + components.add(new Key(Component.Identifier.Key.COMMA)); + return components.toArray(new Component[]{}); + } + + private final void resizeEventQueue(int size) { + processed_events = new Event[size]; + for (int i = 0; i < processed_events.length; i++) + processed_events[i] = new Event(); + processed_events_index = 0; + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + resizeEventQueue(size); + } + + public final synchronized void eventDispatched(AWTEvent event) { + if (event instanceof KeyEvent) + awt_events.add((KeyEvent)event); + } + + public final synchronized void pollDevice() throws IOException { + for (int i = 0; i < awt_events.size(); i++) { + processEvent(awt_events.get(i)); + } + awt_events.clear(); + } + + private final void processEvent(KeyEvent event) { + Component.Identifier.Key key_id = AWTKeyMap.map(event); + if (key_id == null) + return; + Key key = (Key)getComponent(key_id); + if (key == null) + return; + long nanos = event.getWhen()*1000000L; + if (event.getID() == KeyEvent.KEY_PRESSED) { + //the key was pressed + addEvent(key, 1, nanos); + } else if (event.getID() == KeyEvent.KEY_RELEASED) { + KeyEvent nextPress = (KeyEvent)Toolkit.getDefaultToolkit().getSystemEventQueue().peekEvent(KeyEvent.KEY_PRESSED); + if ((nextPress == null) || (nextPress.getWhen() != event.getWhen())) { + //the key came really came up + addEvent(key, 0, nanos); + } + } + } + + private final void addEvent(Key key, float value, long nanos) { + key.setValue(value); + if (processed_events_index < processed_events.length) + processed_events[processed_events_index++].set(key, value, nanos); + } + + protected final synchronized boolean getNextDeviceEvent(Event event) throws IOException { + if (processed_events_index == 0) + return false; + processed_events_index--; + event.set(processed_events[0]); + Event tmp = processed_events[0]; + processed_events[0] = processed_events[processed_events_index]; + processed_events[processed_events_index] = tmp; + return true; + } + + + private final static class Key extends AbstractComponent { + private float value; + + public Key(Component.Identifier.Key key_id) { + super(key_id.getName(), key_id); + } + + public final void setValue(float value) { + this.value = value; + } + + protected final float poll() { + return value; + } + + public final boolean isAnalog() { + return false; + } + + public final boolean isRelative() { + return false; + } + } +} diff --git a/src/plugins/awt/net/java/games/input/AWTMouse.java b/src/plugins/awt/net/java/games/input/AWTMouse.java new file mode 100644 index 0000000..8f8cb06 --- /dev/null +++ b/src/plugins/awt/net/java/games/input/AWTMouse.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2004 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ + +package net.java.games.input; + +import java.awt.AWTEvent; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; + +import java.util.List; +import java.util.ArrayList; + +import java.io.IOException; + +/** + * @author Jeremy + * @author elias + */ +final class AWTMouse extends Mouse implements AWTEventListener { + private final static int EVENT_X = 1; + private final static int EVENT_Y = 2; + private final static int EVENT_BUTTON = 4; + + private final List awt_events = new ArrayList<>(); + private final List processed_awt_events = new ArrayList<>(); + + private int event_state = EVENT_X; + + protected AWTMouse() { + super("AWTMouse", createComponents(), new Controller[]{}, new Rumbler[]{}); + Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK); + } + + private final static Component[] createComponents() { + return new Component[]{new Axis(Component.Identifier.Axis.X), + new Axis(Component.Identifier.Axis.Y), + new Axis(Component.Identifier.Axis.Z), + new Button(Component.Identifier.Button.LEFT), + new Button(Component.Identifier.Button.MIDDLE), + new Button(Component.Identifier.Button.RIGHT)}; + } + + private final void processButtons(int button_enum, float value) { + Button button = getButton(button_enum); + if (button != null) + button.setValue(value); + } + + private final Button getButton(int button_enum) { + switch (button_enum) { + case MouseEvent.BUTTON1: + return (Button)getLeft(); + case MouseEvent.BUTTON2: + return (Button)getMiddle(); + case MouseEvent.BUTTON3: + return (Button)getRight(); + case MouseEvent.NOBUTTON: + default: + // Unknown button + return null; + } + } + + private final void processEvent(AWTEvent event) throws IOException { + if (event instanceof MouseWheelEvent) { + MouseWheelEvent mwe = (MouseWheelEvent)event; + Axis wheel = (Axis)getWheel(); + wheel.setValue(wheel.poll() + mwe.getWheelRotation()); + } else if (event instanceof MouseEvent) { + MouseEvent me = (MouseEvent)event; + Axis x = (Axis)getX(); + Axis y = (Axis)getY(); + x.setValue(me.getX()); + y.setValue(me.getY()); + switch (me.getID()) { + case MouseEvent.MOUSE_PRESSED: + processButtons(me.getButton(), 1f); + break; + case MouseEvent.MOUSE_RELEASED: + processButtons(me.getButton(), 0f); + break; + default: + break; + } + } + } + + public final synchronized void pollDevice() throws IOException { + Axis wheel = (Axis)getWheel(); + wheel.setValue(0); + for (int i = 0; i < awt_events.size(); i++) { + AWTEvent event = awt_events.get(i); + processEvent(event); + processed_awt_events.add(event); + } + awt_events.clear(); + } + + protected final synchronized boolean getNextDeviceEvent(Event event) throws IOException { + while (true) { + if (processed_awt_events.isEmpty()) + return false; + AWTEvent awt_event = processed_awt_events.get(0); + if (awt_event instanceof MouseWheelEvent) { + MouseWheelEvent awt_wheel_event = (MouseWheelEvent)awt_event; + long nanos = awt_wheel_event.getWhen()*1000000L; + event.set(getWheel(), awt_wheel_event.getWheelRotation(), nanos); + processed_awt_events.remove(0); + } else if (awt_event instanceof MouseEvent) { + MouseEvent mouse_event = (MouseEvent)awt_event; + long nanos = mouse_event.getWhen()*1000000L; + switch (event_state) { + case EVENT_X: + event_state = EVENT_Y; + event.set(getX(), mouse_event.getX(), nanos); + return true; + case EVENT_Y: + event_state = EVENT_BUTTON; + event.set(getY(), mouse_event.getY(), nanos); + return true; + case EVENT_BUTTON: + processed_awt_events.remove(0); + event_state = EVENT_X; + Button button = getButton(mouse_event.getButton()); + if (button != null) { + switch (mouse_event.getID()) { + case MouseEvent.MOUSE_PRESSED: + event.set(button, 1f, nanos); + return true; + case MouseEvent.MOUSE_RELEASED: + event.set(button, 0f, nanos); + return true; + default: + break; + } + } + break; + default: + throw new RuntimeException("Unknown event state: " + event_state); + } + } + } + } + + public final synchronized void eventDispatched(AWTEvent event) { + awt_events.add(event); + } + + final static class Axis extends AbstractComponent { + private float value; + + public Axis(Component.Identifier.Axis axis_id) { + super(axis_id.getName(), axis_id); + } + + public final boolean isRelative() { + return false; + } + + public final boolean isAnalog() { + return true; + } + + protected final void setValue(float value) { + this.value = value; + } + + protected final float poll() throws IOException { + return value; + } + } + + final static class Button extends AbstractComponent { + private float value; + + public Button(Component.Identifier.Button button_id) { + super(button_id.getName(), button_id); + } + + protected final void setValue(float value) { + this.value = value; + } + + protected final float poll() throws IOException { + return value; + } + + public final boolean isAnalog() { + return false; + } + + public final boolean isRelative() { + return false; + } + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxAbsInfo.java b/src/plugins/linux/net/java/games/input/LinuxAbsInfo.java new file mode 100644 index 0000000..a2b9456 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxAbsInfo.java @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +/** + * @author elias + */ +final class LinuxAbsInfo { + private int value; + private int minimum; + private int maximum; + private int fuzz; + private int flat; + + public final void set(int value, int min, int max, int fuzz, int flat) { + this.value = value; + this.minimum = min; + this.maximum = max; + this.fuzz = fuzz; + this.flat = flat; + } + + public final String toString() { + return "AbsInfo: value = " + value + " | min = " + minimum + " | max = " + maximum + " | fuzz = " + fuzz + " | flat = " + flat; + } + + public final int getValue() { + return value; + } + + final int getMax() { + return maximum; + } + + final int getMin() { + return minimum; + } + + final int getFlat() { + return flat; + } + + final int getFuzz() { + return fuzz; + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxAbstractController.java b/src/plugins/linux/net/java/games/input/LinuxAbstractController.java new file mode 100644 index 0000000..7efbfb1 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxAbstractController.java @@ -0,0 +1,74 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents a Linux controller +* @author elias +* @version 1.0 +*/ +final class LinuxAbstractController extends AbstractController { + private final PortType port; + private final LinuxEventDevice device; + private final Type type; + + protected LinuxAbstractController(LinuxEventDevice device, Component[] components, Controller[] children, Rumbler[] rumblers, Type type) throws IOException { + super(device.getName(), components, children, rumblers); + this.device = device; + this.port = device.getPortType(); + this.type = type; + } + + public final PortType getPortType() { + return port; + } + + public final void pollDevice() throws IOException { + device.pollKeyStates(); + } + + protected final boolean getNextDeviceEvent(Event event) throws IOException { + return LinuxControllers.getNextDeviceEvent(event, device); + } + + public Type getType() { + return type; + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxAxisDescriptor.java b/src/plugins/linux/net/java/games/input/LinuxAxisDescriptor.java new file mode 100644 index 0000000..d2e476d --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxAxisDescriptor.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +/** + * @author elias + */ +final class LinuxAxisDescriptor { + private int type; + private int code; + + public final void set(int type, int code) { + this.type = type; + this.code = code; + } + + public final int getType() { + return type; + } + + public final int getCode() { + return code; + } + + public final int hashCode() { + return type ^ code; + } + + public final boolean equals(Object other) { + if (!(other instanceof LinuxAxisDescriptor)) + return false; + LinuxAxisDescriptor descriptor = (LinuxAxisDescriptor)other; + return descriptor.type == type && descriptor.code == code; + } + + public final String toString() { + return "LinuxAxis: type = 0x" + Integer.toHexString(type) + ", code = 0x" + Integer.toHexString(code); + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxCombinedController.java b/src/plugins/linux/net/java/games/input/LinuxCombinedController.java new file mode 100644 index 0000000..a88c7c9 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxCombinedController.java @@ -0,0 +1,32 @@ +package net.java.games.input; + +import java.io.IOException; + +public class LinuxCombinedController extends AbstractController { + + private LinuxAbstractController eventController; + private LinuxJoystickAbstractController joystickController; + + LinuxCombinedController(LinuxAbstractController eventController, LinuxJoystickAbstractController joystickController) { + super(eventController.getName(), joystickController.getComponents(), eventController.getControllers(), eventController.getRumblers()); + this.eventController = eventController; + this.joystickController = joystickController; + } + + protected boolean getNextDeviceEvent(Event event) throws IOException { + return joystickController.getNextDeviceEvent(event); + } + + public final PortType getPortType() { + return eventController.getPortType(); + } + + public final void pollDevice() throws IOException { + eventController.pollDevice(); + joystickController.pollDevice(); + } + + public Type getType() { + return eventController.getType(); + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxComponent.java b/src/plugins/linux/net/java/games/input/LinuxComponent.java new file mode 100644 index 0000000..8016deb --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxComponent.java @@ -0,0 +1,78 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents a linux Axis +* @author elias +* @version 1.0 +*/ +class LinuxComponent extends AbstractComponent { + private final LinuxEventComponent component; + + public LinuxComponent(LinuxEventComponent component) { + super(component.getIdentifier().getName(), component.getIdentifier()); + this.component = component; + } + + public final boolean isRelative() { + return component.isRelative(); + } + + public final boolean isAnalog() { + return component.isAnalog(); + } + + protected float poll() throws IOException { + return convertValue(LinuxControllers.poll(component), component.getDescriptor()); + } + + float convertValue(float value, LinuxAxisDescriptor descriptor) { + return getComponent().convertValue(value); + } + + public final float getDeadZone() { + return component.getDeadZone(); + } + + public final LinuxEventComponent getComponent() { + return component; + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxConstantFF.java b/src/plugins/linux/net/java/games/input/LinuxConstantFF.java new file mode 100644 index 0000000..6610c5a --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxConstantFF.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.io.IOException; + +/** + * @author elias + */ +final class LinuxConstantFF extends LinuxForceFeedbackEffect { + public LinuxConstantFF(LinuxEventDevice device) throws IOException { + super(device); + } + + protected final int upload(int id, float intensity) throws IOException { + int scaled_intensity = Math.round(intensity*0x7fff); + return getDevice().uploadConstantEffect(id, 0, 0, 0, 0, 0, scaled_intensity, 0, 0, 0, 0); + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxControllers.java b/src/plugins/linux/net/java/games/input/LinuxControllers.java new file mode 100644 index 0000000..e5edf81 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxControllers.java @@ -0,0 +1,81 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** helper methods for Linux specific Controllers +* @author elias +* @version 1.0 +*/ +final class LinuxControllers { + private final static LinuxEvent linux_event = new LinuxEvent(); + + /* Declared synchronized to protect linux_event */ + public final static synchronized boolean getNextDeviceEvent(Event event, LinuxEventDevice device) throws IOException { + while (device.getNextEvent(linux_event)) { + LinuxAxisDescriptor descriptor = linux_event.getDescriptor(); + LinuxComponent component = device.mapDescriptor(descriptor); + if (component != null) { + float value = component.convertValue(linux_event.getValue(), descriptor); + event.set(component, value, linux_event.getNanos()); + return true; + } + } + return false; + } + + private final static LinuxAbsInfo abs_info = new LinuxAbsInfo(); + + /* Declared synchronized to protect abs_info */ + public final static synchronized float poll(LinuxEventComponent event_component) throws IOException { + int native_type = event_component.getDescriptor().getType(); + switch (native_type) { + case NativeDefinitions.EV_KEY: + int native_code = event_component.getDescriptor().getCode(); + float state = event_component.getDevice().isKeySet(native_code) ? 1f : 0f; + return state; + case NativeDefinitions.EV_ABS: + event_component.getAbsInfo(abs_info); + return abs_info.getValue(); + default: + throw new RuntimeException("Unkown native_type: " + native_type); + } + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxDevice.java b/src/plugins/linux/net/java/games/input/LinuxDevice.java new file mode 100644 index 0000000..2f26a68 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxDevice.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; + +/** + * @author elias + */ +interface LinuxDevice { + void close() throws IOException; +} diff --git a/src/plugins/linux/net/java/games/input/LinuxDeviceTask.java b/src/plugins/linux/net/java/games/input/LinuxDeviceTask.java new file mode 100644 index 0000000..3dbdeda --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxDeviceTask.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.io.IOException; + +abstract class LinuxDeviceTask { + public final static int INPROGRESS = 1; + public final static int COMPLETED = 2; + public final static int FAILED = 3; + + private Object result; + private IOException exception; + private int state = INPROGRESS; + + public final void doExecute() { + try { + result = execute(); + state = COMPLETED; + } catch (IOException e) { + exception = e; + state = FAILED; + } + } + + public final IOException getException() { + return exception; + } + + public final Object getResult() { + return result; + } + + public final int getState() { + return state; + } + + protected abstract Object execute() throws IOException; +} diff --git a/src/plugins/linux/net/java/games/input/LinuxDeviceThread.java b/src/plugins/linux/net/java/games/input/LinuxDeviceThread.java new file mode 100644 index 0000000..f550c24 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxDeviceThread.java @@ -0,0 +1,91 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +/** + * Linux doesn't have proper support for force feedback + * from different threads since it relies on PIDs + * to determine ownership of a particular effect slot. + * Therefore we have to hack around this by + * making sure everything related to FF + * (including the final device close that performs + * and implicit deletion of all the process' effects) + * is run on a single thread. + */ +final class LinuxDeviceThread extends Thread { + private final List tasks = new ArrayList<>(); + + public LinuxDeviceThread() { + setDaemon(true); + start(); + } + + public synchronized final void run() { + while (true) { + if (!tasks.isEmpty()) { + LinuxDeviceTask task = tasks.remove(0); + task.doExecute(); + synchronized (task) { + task.notify(); + } + } else { + try { + wait(); + } catch (InterruptedException e) { + // ignore + } + } + } + } + + public final Object execute(LinuxDeviceTask task) throws IOException { + synchronized (this) { + tasks.add(task); + notify(); + } + synchronized (task) { + while (task.getState() == LinuxDeviceTask.INPROGRESS) { + try { + task.wait(); + } catch (InterruptedException e) { + // ignore + } + } + } + switch (task.getState()) { + case LinuxDeviceTask.COMPLETED: + return task.getResult(); + case LinuxDeviceTask.FAILED: + throw task.getException(); + default: + throw new RuntimeException("Invalid task state: " + task.getState()); + } + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxEnvironmentPlugin.java b/src/plugins/linux/net/java/games/input/LinuxEnvironmentPlugin.java new file mode 100644 index 0000000..035b8f7 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxEnvironmentPlugin.java @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import net.java.games.util.plugins.Plugin; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.ArrayList; +import java.io.IOException; +import java.io.File; +import java.io.FilenameFilter; +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** Environment plugin for linux + * @author elias + * @author Jeremy Booth (jeremy@newdawnsoftware.com) + */ +public final class LinuxEnvironmentPlugin extends ControllerEnvironment implements Plugin { + private final static String LIBNAME = "jinput-linux"; + private final static String POSTFIX64BIT = "64"; + private static boolean supported = false; + + private final Controller[] controllers; + private final List devices = new ArrayList(); + private final static LinuxDeviceThread device_thread = new LinuxDeviceThread(); + + /** + * Static utility method for loading native libraries. + * It will try to load from either the path given by + * the net.java.games.input.librarypath property + * or through System.loadLibrary(). + * + */ + static void loadLibrary(final String lib_name) { + AccessController.doPrivileged((PrivilegedAction) () -> { + String lib_path = System.getProperty("net.java.games.input.librarypath"); + try { + if(lib_path != null) + System.load(lib_path + File.separator + System.mapLibraryName(lib_name)); + else + System.loadLibrary(lib_name); + } catch(UnsatisfiedLinkError e) { + log("Failed to load library: " + e.getMessage()); + e.printStackTrace(); + supported = false; + } + return null; + }); + } + + static String getPrivilegedProperty(final String property) { + return AccessController.doPrivileged((PrivilegedAction)() -> System.getProperty(property)); + } + + + static String getPrivilegedProperty(final String property, final String default_value) { + return AccessController.doPrivileged((PrivilegedAction)() -> System.getProperty(property, default_value)); + } + + static { + String osName = getPrivilegedProperty("os.name", "").trim(); + if(osName.equals("Linux")) { + supported = true; + if("i386".equals(getPrivilegedProperty("os.arch"))) { + loadLibrary(LIBNAME); + } else { + loadLibrary(LIBNAME + POSTFIX64BIT); + } + } + } + + public final static Object execute(LinuxDeviceTask task) throws IOException { + return device_thread.execute(task); + } + + public LinuxEnvironmentPlugin() { + if(isSupported()) { + this.controllers = enumerateControllers(); + log("Linux plugin claims to have found " + controllers.length + " controllers"); + AccessController.doPrivileged((PrivilegedAction)() -> { + Runtime.getRuntime().addShutdownHook(new ShutdownHook()); + return null; + }); + } else { + controllers = new Controller[0]; + } + } + + /** Returns a list of all controllers available to this environment, + * or an empty array if there are no controllers in this environment. + * @return Returns a list of all controllers available to this environment, + * or an empty array if there are no controllers in this environment. + */ + public final Controller[] getControllers() { + return controllers; + } + + private final static Component[] createComponents(List event_components, LinuxEventDevice device) { + LinuxEventComponent[][] povs = new LinuxEventComponent[4][2]; + List components = new ArrayList<>(); + for(int i = 0; i < event_components.size(); i++) { + LinuxEventComponent event_component = event_components.get(i); + Component.Identifier identifier = event_component.getIdentifier(); + + if(identifier == Component.Identifier.Axis.POV) { + int native_code = event_component.getDescriptor().getCode(); + switch(native_code) { + case NativeDefinitions.ABS_HAT0X: + povs[0][0] = event_component; + break; + case NativeDefinitions.ABS_HAT0Y: + povs[0][1] = event_component; + break; + case NativeDefinitions.ABS_HAT1X: + povs[1][0] = event_component; + break; + case NativeDefinitions.ABS_HAT1Y: + povs[1][1] = event_component; + break; + case NativeDefinitions.ABS_HAT2X: + povs[2][0] = event_component; + break; + case NativeDefinitions.ABS_HAT2Y: + povs[2][1] = event_component; + break; + case NativeDefinitions.ABS_HAT3X: + povs[3][0] = event_component; + break; + case NativeDefinitions.ABS_HAT3Y: + povs[3][1] = event_component; + break; + default: + log("Unknown POV instance: " + native_code); + break; + } + } else if(identifier != null) { + LinuxComponent component = new LinuxComponent(event_component); + components.add(component); + device.registerComponent(event_component.getDescriptor(), component); + } + } + for(int i = 0; i < povs.length; i++) { + LinuxEventComponent x = povs[i][0]; + LinuxEventComponent y = povs[i][1]; + if(x != null && y != null) { + LinuxComponent controller_component = new LinuxPOV(x, y); + components.add(controller_component); + device.registerComponent(x.getDescriptor(), controller_component); + device.registerComponent(y.getDescriptor(), controller_component); + } + } + Component[] components_array = new Component[components.size()]; + components.toArray(components_array); + return components_array; + } + + private final static Mouse createMouseFromDevice(LinuxEventDevice device, Component[] components) throws IOException { + Mouse mouse = new LinuxMouse(device, components, new Controller[]{}, device.getRumblers()); + if(mouse.getX() != null && mouse.getY() != null && mouse.getPrimaryButton() != null) + return mouse; + else + return null; + } + + private final static Keyboard createKeyboardFromDevice(LinuxEventDevice device, Component[] components) throws IOException { + Keyboard keyboard = new LinuxKeyboard(device, components, new Controller[]{}, device.getRumblers()); + return keyboard; + } + + private final static Controller createJoystickFromDevice(LinuxEventDevice device, Component[] components, Controller.Type type) throws IOException { + Controller joystick = new LinuxAbstractController(device, components, new Controller[]{}, device.getRumblers(), type); + return joystick; + } + + private final static Controller createControllerFromDevice(LinuxEventDevice device) throws IOException { + List event_components = device.getComponents(); + Component[] components = createComponents(event_components, device); + Controller.Type type = device.getType(); + + if(type == Controller.Type.MOUSE) { + return createMouseFromDevice(device, components); + } else if(type == Controller.Type.KEYBOARD) { + return createKeyboardFromDevice(device, components); + } else if(type == Controller.Type.STICK || type == Controller.Type.GAMEPAD) { + return createJoystickFromDevice(device, components, type); + } else + return null; + } + + private final Controller[] enumerateControllers() { + List controllers = new ArrayList<>(); + List eventControllers = new ArrayList<>(); + List jsControllers = new ArrayList<>(); + enumerateEventControllers(eventControllers); + enumerateJoystickControllers(jsControllers); + + for(int i = 0; i < eventControllers.size(); i++) { + for(int j = 0; j < jsControllers.size(); j++) { + Controller evController = eventControllers.get(i); + Controller jsController = jsControllers.get(j); + + // compare + // Check if the nodes have the same name + if(evController.getName().equals(jsController.getName())) { + // Check they have the same component count + Component[] evComponents = evController.getComponents(); + Component[] jsComponents = jsController.getComponents(); + if(evComponents.length == jsComponents.length) { + boolean foundADifference = false; + // check the component pairs are of the same type + for(int k = 0; k < evComponents.length; k++) { + // Check the type of the component is the same + if(!(evComponents[k].getIdentifier() == jsComponents[k].getIdentifier())) { + foundADifference = true; + } + } + + if(!foundADifference) { + controllers.add(new LinuxCombinedController((LinuxAbstractController) eventControllers.remove(i), (LinuxJoystickAbstractController) jsControllers.remove(j))); + i--; + j--; + break; + } + } + } + } + } + controllers.addAll(eventControllers); + controllers.addAll(jsControllers); + + Controller[] controllers_array = new Controller[controllers.size()]; + controllers.toArray(controllers_array); + return controllers_array; + } + + private final static Component.Identifier.Button getButtonIdentifier(int index) { + switch(index) { + case 0: + return Component.Identifier.Button._0; + case 1: + return Component.Identifier.Button._1; + case 2: + return Component.Identifier.Button._2; + case 3: + return Component.Identifier.Button._3; + case 4: + return Component.Identifier.Button._4; + case 5: + return Component.Identifier.Button._5; + case 6: + return Component.Identifier.Button._6; + case 7: + return Component.Identifier.Button._7; + case 8: + return Component.Identifier.Button._8; + case 9: + return Component.Identifier.Button._9; + case 10: + return Component.Identifier.Button._10; + case 11: + return Component.Identifier.Button._11; + case 12: + return Component.Identifier.Button._12; + case 13: + return Component.Identifier.Button._13; + case 14: + return Component.Identifier.Button._14; + case 15: + return Component.Identifier.Button._15; + case 16: + return Component.Identifier.Button._16; + case 17: + return Component.Identifier.Button._17; + case 18: + return Component.Identifier.Button._18; + case 19: + return Component.Identifier.Button._19; + case 20: + return Component.Identifier.Button._20; + case 21: + return Component.Identifier.Button._21; + case 22: + return Component.Identifier.Button._22; + case 23: + return Component.Identifier.Button._23; + case 24: + return Component.Identifier.Button._24; + case 25: + return Component.Identifier.Button._25; + case 26: + return Component.Identifier.Button._26; + case 27: + return Component.Identifier.Button._27; + case 28: + return Component.Identifier.Button._28; + case 29: + return Component.Identifier.Button._29; + case 30: + return Component.Identifier.Button._30; + case 31: + return Component.Identifier.Button._31; + default: + return null; + } + } + + private final static Controller createJoystickFromJoystickDevice(LinuxJoystickDevice device) { + List components = new ArrayList<>(); + byte[] axisMap = device.getAxisMap(); + char[] buttonMap = device.getButtonMap(); + LinuxJoystickAxis[] hatBits = new LinuxJoystickAxis[6]; + + for(int i = 0; i < device.getNumButtons(); i++) { + Component.Identifier button_id = LinuxNativeTypesMap.getButtonID(buttonMap[i]); + if(button_id != null) { + LinuxJoystickButton button = new LinuxJoystickButton(button_id); + device.registerButton(i, button); + components.add(button); + } + } + for(int i = 0; i < device.getNumAxes(); i++) { + Component.Identifier.Axis axis_id; + axis_id = (Component.Identifier.Axis) LinuxNativeTypesMap.getAbsAxisID(axisMap[i]); + LinuxJoystickAxis axis = new LinuxJoystickAxis(axis_id); + + device.registerAxis(i, axis); + + if(axisMap[i] == NativeDefinitions.ABS_HAT0X) { + hatBits[0] = axis; + } else if(axisMap[i] == NativeDefinitions.ABS_HAT0Y) { + hatBits[1] = axis; + axis = new LinuxJoystickPOV(Component.Identifier.Axis.POV, hatBits[0], hatBits[1]); + device.registerPOV((LinuxJoystickPOV) axis); + components.add(axis); + } else if(axisMap[i] == NativeDefinitions.ABS_HAT1X) { + hatBits[2] = axis; + } else if(axisMap[i] == NativeDefinitions.ABS_HAT1Y) { + hatBits[3] = axis; + axis = new LinuxJoystickPOV(Component.Identifier.Axis.POV, hatBits[2], hatBits[3]); + device.registerPOV((LinuxJoystickPOV) axis); + components.add(axis); + } else if(axisMap[i] == NativeDefinitions.ABS_HAT2X) { + hatBits[4] = axis; + } else if(axisMap[i] == NativeDefinitions.ABS_HAT2Y) { + hatBits[5] = axis; + axis = new LinuxJoystickPOV(Component.Identifier.Axis.POV, hatBits[4], hatBits[5]); + device.registerPOV((LinuxJoystickPOV) axis); + components.add(axis); + } else { + components.add(axis); + } + } + + return new LinuxJoystickAbstractController(device, components.toArray(new Component[]{}), new Controller[]{}, new Rumbler[]{}); + } + + private final void enumerateJoystickControllers(List controllers) { + File[] joystick_device_files = enumerateJoystickDeviceFiles("/dev/input"); + if(joystick_device_files == null || joystick_device_files.length == 0) { + joystick_device_files = enumerateJoystickDeviceFiles("/dev"); + if(joystick_device_files == null) + return; + } + for(int i = 0; i < joystick_device_files.length; i++) { + File event_file = joystick_device_files[i]; + try { + String path = getAbsolutePathPrivileged(event_file); + LinuxJoystickDevice device = new LinuxJoystickDevice(path); + Controller controller = createJoystickFromJoystickDevice(device); + if(controller != null) { + controllers.add(controller); + devices.add(device); + } else + device.close(); + } catch(IOException e) { + log("Failed to open device (" + event_file + "): " + e.getMessage()); + } + } + } + + private final static File[] enumerateJoystickDeviceFiles(final String dev_path) { + final File dev = new File(dev_path); + return listFilesPrivileged(dev, new FilenameFilter() { + public final boolean accept(File dir, String name) { + return name.startsWith("js"); + } + }); + } + + private static String getAbsolutePathPrivileged(final File file) { + return AccessController.doPrivileged((PrivilegedAction) () -> file.getAbsolutePath()); + } + + private static File[] listFilesPrivileged(final File dir, final FilenameFilter filter) { + return AccessController.doPrivileged((PrivilegedAction) () -> { + File[] files = dir.listFiles(filter); + if(files == null) { + log("dir " + dir.getName() + " exists: " + dir.exists() + ", is writable: " + dir.isDirectory()); + files = new File[]{}; + } else { + Arrays.sort(files, Comparator.comparing(File::getName)); + } + return files; + }); + } + + private final void enumerateEventControllers(List controllers) { + final File dev = new File("/dev/input"); + File[] event_device_files = listFilesPrivileged(dev, (File dir, String name) -> name.startsWith("event")); + + if(event_device_files == null) + return; + for(int i = 0; i < event_device_files.length; i++) { + File event_file = event_device_files[i]; + try { + String path = getAbsolutePathPrivileged(event_file); + LinuxEventDevice device = new LinuxEventDevice(path); + try { + Controller controller = createControllerFromDevice(device); + if(controller != null) { + controllers.add(controller); + devices.add(device); + } else + device.close(); + } catch(IOException e) { + log("Failed to create Controller: " + e.getMessage()); + device.close(); + } + } catch(IOException e) { + log("Failed to open device (" + event_file + "): " + e.getMessage()); + } + } + } + + private final class ShutdownHook extends Thread { + public final void run() { + for(int i = 0; i < devices.size(); i++) { + try { + LinuxDevice device = devices.get(i); + device.close(); + } catch(IOException e) { + log("Failed to close device: " + e.getMessage()); + } + } + } + } + + public boolean isSupported() { + return supported; + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxEvent.java b/src/plugins/linux/net/java/games/input/LinuxEvent.java new file mode 100644 index 0000000..de2959c --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxEvent.java @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +/** + * @author elias + */ +final class LinuxEvent { + private long nanos; + private final LinuxAxisDescriptor descriptor = new LinuxAxisDescriptor(); + private int value; + + public final void set(long seconds, long microseconds, int type, int code, int value) { + this.nanos = (seconds*1000000 + microseconds)*1000; + this.descriptor.set(type, code); + this.value = value; + } + + public final int getValue() { + return value; + } + + public final LinuxAxisDescriptor getDescriptor() { + return descriptor; + } + + public final long getNanos() { + return nanos; + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxEventComponent.java b/src/plugins/linux/net/java/games/input/LinuxEventComponent.java new file mode 100644 index 0000000..b2ad562 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxEventComponent.java @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.io.IOException; + +/** + * @author elias + * @author Jeremy Booth (jeremy@newdawnsoftware.com) + */ +final class LinuxEventComponent { + private final LinuxEventDevice device; + private final Component.Identifier identifier; + private final Controller.Type button_trait; + private final boolean is_relative; + private final LinuxAxisDescriptor descriptor; + private final int min; + private final int max; + private final int flat; + + + public LinuxEventComponent(LinuxEventDevice device, Component.Identifier identifier, boolean is_relative, int native_type, int native_code) throws IOException { + this.device = device; + this.identifier = identifier; + if (native_type == NativeDefinitions.EV_KEY) + this.button_trait = LinuxNativeTypesMap.guessButtonTrait(native_code); + else + this.button_trait = Controller.Type.UNKNOWN; + this.is_relative = is_relative; + this.descriptor = new LinuxAxisDescriptor(); + descriptor.set(native_type, native_code); + if (native_type == NativeDefinitions.EV_ABS) { + LinuxAbsInfo abs_info = new LinuxAbsInfo(); + getAbsInfo(abs_info); + this.min = abs_info.getMin(); + this.max = abs_info.getMax(); + this.flat = abs_info.getFlat(); + } else { + this.min = Integer.MIN_VALUE; + this.max = Integer.MAX_VALUE; + this.flat = 0; + } + } + + public final LinuxEventDevice getDevice() { + return device; + } + + public final void getAbsInfo(LinuxAbsInfo abs_info) throws IOException { + assert descriptor.getType() == NativeDefinitions.EV_ABS; + device.getAbsInfo(descriptor.getCode(), abs_info); + } + + public final Controller.Type getButtonTrait() { + return button_trait; + } + + public final Component.Identifier getIdentifier() { + return identifier; + } + + public final LinuxAxisDescriptor getDescriptor() { + return descriptor; + } + + public final boolean isRelative() { + return is_relative; + } + + public final boolean isAnalog() { + return identifier instanceof Component.Identifier.Axis && identifier != Component.Identifier.Axis.POV; + } + + final float convertValue(float value) { + if (identifier instanceof Component.Identifier.Axis && !is_relative) { + // Some axes have min = max = 0 + if (min == max) + return 0; + if (value > max) + value = max; + else if (value < min) + value = min; + return 2*(value - min)/(max - min) - 1; + } else { + return value; + } + } + + final float getDeadZone() { + return flat/(2f*(max - min)); + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxEventDevice.java b/src/plugins/linux/net/java/games/input/LinuxEventDevice.java new file mode 100644 index 0000000..a69f9e8 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxEventDevice.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; + +/** + * @author elias + */ +final class LinuxEventDevice implements LinuxDevice { + private final Map component_map = new HashMap<>(); + private final Rumbler[] rumblers; + private final long fd; + private final String name; + private final LinuxInputID input_id; + private final List components; + private final Controller.Type type; + + /* Closed state variable that protects the validity of the file descriptor. + * Access to the closed state must be synchronized + */ + private boolean closed; + + /* Access to the key_states array could be synchronized, but + * it doesn't hurt to have multiple threads read/write from/to it + */ + private final byte[] key_states = new byte[NativeDefinitions.KEY_MAX/8 + 1]; + + public LinuxEventDevice(String filename) throws IOException { + long fd; + boolean detect_rumblers = true; + try { + fd = nOpen(filename, true); + } catch (IOException e) { + fd = nOpen(filename, false); + detect_rumblers = false; + } + this.fd = fd; + try { + this.name = getDeviceName(); + this.input_id = getDeviceInputID(); + this.components = getDeviceComponents(); + if (detect_rumblers) + this.rumblers = enumerateRumblers(); + else + this.rumblers = new Rumbler[]{}; + this.type = guessType(); + } catch (IOException e) { + close(); + throw e; + } + } + private final static native long nOpen(String filename, boolean rw) throws IOException; + + public final Controller.Type getType() { + return type; + } + + private final static int countComponents(List components, Class id_type, boolean relative) { + int count = 0; + for (int i = 0; i < components.size(); i++) { + LinuxEventComponent component = components.get(i); + if (id_type.isInstance(component.getIdentifier()) && relative == component.isRelative()) + count++; + } + return count; + } + + private final Controller.Type guessType() throws IOException { + List components = getComponents(); + if (components.size() == 0) + return Controller.Type.UNKNOWN; + int num_rel_axes = countComponents(components, Component.Identifier.Axis.class, true); + int num_abs_axes = countComponents(components, Component.Identifier.Axis.class, false); + int num_keys = countComponents(components, Component.Identifier.Key.class, false); + int mouse_traits = 0; + int keyboard_traits = 0; + int joystick_traits = 0; + int gamepad_traits = 0; + if (name.toLowerCase().indexOf("mouse") != -1) + mouse_traits++; + if (name.toLowerCase().indexOf("keyboard") != -1) + keyboard_traits++; + if (name.toLowerCase().indexOf("joystick") != -1) + joystick_traits++; + if (name.toLowerCase().indexOf("gamepad") != -1) + gamepad_traits++; + int num_keyboard_button_traits = 0; + int num_mouse_button_traits = 0; + int num_joystick_button_traits = 0; + int num_gamepad_button_traits = 0; + // count button traits + for (int i = 0; i < components.size(); i++) { + LinuxEventComponent component = components.get(i); + if (component.getButtonTrait() == Controller.Type.MOUSE) + num_mouse_button_traits++; + else if (component.getButtonTrait() == Controller.Type.KEYBOARD) + num_keyboard_button_traits++; + else if (component.getButtonTrait() == Controller.Type.GAMEPAD) + num_gamepad_button_traits++; + else if (component.getButtonTrait() == Controller.Type.STICK) + num_joystick_button_traits++; + } + if ((num_mouse_button_traits >= num_keyboard_button_traits) && (num_mouse_button_traits >= num_joystick_button_traits) && (num_mouse_button_traits >= num_gamepad_button_traits)) { + mouse_traits++; + } else if ((num_keyboard_button_traits >= num_mouse_button_traits) && (num_keyboard_button_traits >= num_joystick_button_traits) && (num_keyboard_button_traits >= num_gamepad_button_traits)) { + keyboard_traits++; + } else if ((num_joystick_button_traits >= num_keyboard_button_traits) && (num_joystick_button_traits >= num_mouse_button_traits) && (num_joystick_button_traits >= num_gamepad_button_traits)) { + joystick_traits++; + } else if ((num_gamepad_button_traits >= num_keyboard_button_traits) && (num_gamepad_button_traits >= num_mouse_button_traits) && (num_gamepad_button_traits >= num_joystick_button_traits)) { + gamepad_traits++; + } + if (num_rel_axes >= 2) { + mouse_traits++; + } + if (num_abs_axes >= 2) { + joystick_traits++; + gamepad_traits++; + } + + if ((mouse_traits >= keyboard_traits) && (mouse_traits >= joystick_traits) && (mouse_traits >= gamepad_traits)) { + return Controller.Type.MOUSE; + } else if ((keyboard_traits >= mouse_traits) && (keyboard_traits >= joystick_traits) && (keyboard_traits >= gamepad_traits)) { + return Controller.Type.KEYBOARD; + } else if ((joystick_traits >= mouse_traits) && (joystick_traits >= keyboard_traits) && (joystick_traits >= gamepad_traits)) { + return Controller.Type.STICK; + } else if ((gamepad_traits >= mouse_traits) && (gamepad_traits >= keyboard_traits) && (gamepad_traits >= joystick_traits)) { + return Controller.Type.GAMEPAD; + } else + return null; + } + + private final Rumbler[] enumerateRumblers() { + List rumblers = new ArrayList<>(); + try { + int num_effects = getNumEffects(); + if (num_effects <= 0) + return rumblers.toArray(new Rumbler[]{}); + byte[] ff_bits = getForceFeedbackBits(); + if (isBitSet(ff_bits, NativeDefinitions.FF_RUMBLE) && num_effects > rumblers.size()) { + rumblers.add(new LinuxRumbleFF(this)); + } + } catch (IOException e) { + LinuxEnvironmentPlugin.log("Failed to enumerate rumblers: " + e.getMessage()); + } + return rumblers.toArray(new Rumbler[]{}); + } + + public final Rumbler[] getRumblers() { + return rumblers; + } + + public final synchronized int uploadRumbleEffect(int id, int trigger_button, int direction, int trigger_interval, int replay_length, int replay_delay, int strong_magnitude, int weak_magnitude) throws IOException { + checkClosed(); + return nUploadRumbleEffect(fd, id, direction, trigger_button, trigger_interval, replay_length, replay_delay, strong_magnitude, weak_magnitude); + } + private final static native int nUploadRumbleEffect(long fd, int id, int direction, int trigger_button, int trigger_interval, int replay_length, int replay_delay, int strong_magnitude, int weak_magnitude) throws IOException; + + public final synchronized int uploadConstantEffect(int id, int trigger_button, int direction, int trigger_interval, int replay_length, int replay_delay, int constant_level, int constant_env_attack_length, int constant_env_attack_level, int constant_env_fade_length, int constant_env_fade_level) throws IOException { + checkClosed(); + return nUploadConstantEffect(fd, id, direction, trigger_button, trigger_interval, replay_length, replay_delay, constant_level, constant_env_attack_length, constant_env_attack_level, constant_env_fade_length, constant_env_fade_level); + } + private final static native int nUploadConstantEffect(long fd, int id, int direction, int trigger_button, int trigger_interval, int replay_length, int replay_delay, int constant_level, int constant_env_attack_length, int constant_env_attack_level, int constant_env_fade_length, int constant_env_fade_level) throws IOException; + + final void eraseEffect(int id) throws IOException { + nEraseEffect(fd, id); + } + private final static native void nEraseEffect(long fd, int ff_id) throws IOException; + + public final synchronized void writeEvent(int type, int code, int value) throws IOException { + checkClosed(); + nWriteEvent(fd, type, code, value); + } + private final static native void nWriteEvent(long fd, int type, int code, int value) throws IOException; + + public final void registerComponent(LinuxAxisDescriptor desc, LinuxComponent component) { + component_map.put(desc, component); + } + + public final LinuxComponent mapDescriptor(LinuxAxisDescriptor desc) { + return component_map.get(desc); + } + + public final Controller.PortType getPortType() throws IOException { + return input_id.getPortType(); + } + + public final LinuxInputID getInputID() { + return input_id; + } + + private final LinuxInputID getDeviceInputID() throws IOException { + return nGetInputID(fd); + } + private final static native LinuxInputID nGetInputID(long fd) throws IOException; + + public final int getNumEffects() throws IOException { + return nGetNumEffects(fd); + } + private final static native int nGetNumEffects(long fd) throws IOException; + + private final int getVersion() throws IOException { + return nGetVersion(fd); + } + private final static native int nGetVersion(long fd) throws IOException; + + public final synchronized boolean getNextEvent(LinuxEvent linux_event) throws IOException { + checkClosed(); + return nGetNextEvent(fd, linux_event); + } + private final static native boolean nGetNextEvent(long fd, LinuxEvent linux_event) throws IOException; + + public final synchronized void getAbsInfo(int abs_axis, LinuxAbsInfo abs_info) throws IOException { + checkClosed(); + nGetAbsInfo(fd, abs_axis, abs_info); + } + private final static native void nGetAbsInfo(long fd, int abs_axis, LinuxAbsInfo abs_info) throws IOException; + + private final void addKeys(List components) throws IOException { + byte[] bits = getKeysBits(); + for (int i = 0; i < bits.length*8; i++) { + if (isBitSet(bits, i)) { + Component.Identifier id = LinuxNativeTypesMap.getButtonID(i); + components.add(new LinuxEventComponent(this, id, false, NativeDefinitions.EV_KEY, i)); + } + } + } + + private final void addAbsoluteAxes(List components) throws IOException { + byte[] bits = getAbsoluteAxesBits(); + for (int i = 0; i < bits.length*8; i++) { + if (isBitSet(bits, i)) { + Component.Identifier id = LinuxNativeTypesMap.getAbsAxisID(i); + components.add(new LinuxEventComponent(this, id, false, NativeDefinitions.EV_ABS, i)); + } + } + } + + private final void addRelativeAxes(List components) throws IOException { + byte[] bits = getRelativeAxesBits(); + for (int i = 0; i < bits.length*8; i++) { + if (isBitSet(bits, i)) { + Component.Identifier id = LinuxNativeTypesMap.getRelAxisID(i); + components.add(new LinuxEventComponent(this, id, true, NativeDefinitions.EV_REL, i)); + } + } + } + + public final List getComponents() { + return components; + } + + private final List getDeviceComponents() throws IOException { + List components = new ArrayList<>(); + byte[] evtype_bits = getEventTypeBits(); + if (isBitSet(evtype_bits, NativeDefinitions.EV_KEY)) + addKeys(components); + if (isBitSet(evtype_bits, NativeDefinitions.EV_ABS)) + addAbsoluteAxes(components); + if (isBitSet(evtype_bits, NativeDefinitions.EV_REL)) + addRelativeAxes(components); + return components; + } + + private final byte[] getForceFeedbackBits() throws IOException { + byte[] bits = new byte[NativeDefinitions.FF_MAX/8 + 1]; + nGetBits(fd, NativeDefinitions.EV_FF, bits); + return bits; + } + + private final byte[] getKeysBits() throws IOException { + byte[] bits = new byte[NativeDefinitions.KEY_MAX/8 + 1]; + nGetBits(fd, NativeDefinitions.EV_KEY, bits); + return bits; + } + + private final byte[] getAbsoluteAxesBits() throws IOException { + byte[] bits = new byte[NativeDefinitions.ABS_MAX/8 + 1]; + nGetBits(fd, NativeDefinitions.EV_ABS, bits); + return bits; + } + + private final byte[] getRelativeAxesBits() throws IOException { + byte[] bits = new byte[NativeDefinitions.REL_MAX/8 + 1]; + nGetBits(fd, NativeDefinitions.EV_REL, bits); + return bits; + } + + private final byte[] getEventTypeBits() throws IOException { + byte[] bits = new byte[NativeDefinitions.EV_MAX/8 + 1]; + nGetBits(fd, 0, bits); + return bits; + } + private final static native void nGetBits(long fd, int ev_type, byte[] evtype_bits) throws IOException; + + public final synchronized void pollKeyStates() throws IOException { + nGetKeyStates(fd, key_states); + } + private final static native void nGetKeyStates(long fd, byte[] states) throws IOException; + + public final boolean isKeySet(int bit) { + return isBitSet(key_states, bit); + } + + public final static boolean isBitSet(byte[] bits, int bit) { + return (bits[bit/8] & (1<<(bit%8))) != 0; + } + + public final String getName() { + return name; + } + + private final String getDeviceName() throws IOException { + return nGetName(fd); + } + private final static native String nGetName(long fd) throws IOException; + + public synchronized final void close() throws IOException { + if (closed) + return; + closed = true; + LinuxEnvironmentPlugin.execute(new LinuxDeviceTask() { + protected final Object execute() throws IOException { + nClose(fd); + return null; + } + }); + } + private final static native void nClose(long fd) throws IOException; + + private final void checkClosed() throws IOException { + if (closed) + throw new IOException("Device is closed"); + } + + @SuppressWarnings("deprecation") + protected void finalize() throws IOException { + close(); + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxForceFeedbackEffect.java b/src/plugins/linux/net/java/games/input/LinuxForceFeedbackEffect.java new file mode 100644 index 0000000..3d134ba --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxForceFeedbackEffect.java @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.io.IOException; + +/** + * @author elias + */ +abstract class LinuxForceFeedbackEffect implements Rumbler { + private final LinuxEventDevice device; + private final int ff_id; + private final WriteTask write_task = new WriteTask(); + private final UploadTask upload_task = new UploadTask(); + + public LinuxForceFeedbackEffect(LinuxEventDevice device) throws IOException { + this.device = device; + this.ff_id = upload_task.doUpload(-1, 0); + } + + protected abstract int upload(int id, float intensity) throws IOException; + + protected final LinuxEventDevice getDevice() { + return device; + } + + public synchronized final void rumble(float intensity) { + try { + if (intensity > 0) { + upload_task.doUpload(ff_id, intensity); + write_task.write(1); + } else { + write_task.write(0); + } + } catch (IOException e) { + LinuxEnvironmentPlugin.log("Failed to rumble: " + e); + } + } + + /* + * Erase doesn't seem to be implemented on Logitech joysticks, + * so we'll rely on the kernel cleaning up on device close + */ +/* + public final void erase() throws IOException { + device.eraseEffect(ff_id); + } +*/ + public final String getAxisName() { + return null; + } + + public final Component.Identifier getAxisIdentifier() { + return null; + } + + private final class UploadTask extends LinuxDeviceTask { + private int id; + private float intensity; + + public final int doUpload(int id, float intensity) throws IOException { + this.id = id; + this.intensity = intensity; + LinuxEnvironmentPlugin.execute(this); + return this.id; + } + + protected final Object execute() throws IOException { + this.id = upload(id, intensity); + return null; + } + } + + private final class WriteTask extends LinuxDeviceTask { + private int value; + + public final void write(int value) throws IOException { + this.value = value; + LinuxEnvironmentPlugin.execute(this); + } + + protected final Object execute() throws IOException { + device.writeEvent(NativeDefinitions.EV_FF, ff_id, value); + return null; + } + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxInputID.java b/src/plugins/linux/net/java/games/input/LinuxInputID.java new file mode 100644 index 0000000..bed8fd3 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxInputID.java @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +/** + * @author elias + */ +final class LinuxInputID { + private final int bustype; + private final int vendor; + private final int product; + private final int version; + + public LinuxInputID(int bustype, int vendor, int product, int version) { + this.bustype = bustype; + this.vendor = vendor; + this.product = product; + this.version = version; + } + + public final Controller.PortType getPortType() { + return LinuxNativeTypesMap.getPortType(bustype); + } + + public final String toString() { + return "LinuxInputID: bustype = 0x" + Integer.toHexString(bustype) + " | vendor = 0x" + Integer.toHexString(vendor) + + " | product = 0x" + Integer.toHexString(product) + " | version = 0x" + Integer.toHexString(version); + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxJoystickAbstractController.java b/src/plugins/linux/net/java/games/input/LinuxJoystickAbstractController.java new file mode 100644 index 0000000..36633ad --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxJoystickAbstractController.java @@ -0,0 +1,70 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents a Linux controller +* @author elias +* @version 1.0 +*/ +final class LinuxJoystickAbstractController extends AbstractController { + private final LinuxJoystickDevice device; + + protected LinuxJoystickAbstractController(LinuxJoystickDevice device, Component[] components, Controller[] children, Rumbler[] rumblers) { + super(device.getName(), components, children, rumblers); + this.device = device; + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + device.setBufferSize(size); + } + + public final void pollDevice() throws IOException { + device.poll(); + } + + protected final boolean getNextDeviceEvent(Event event) throws IOException { + return device.getNextEvent(event); + } + + public Type getType() { + return Controller.Type.STICK; + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxJoystickAxis.java b/src/plugins/linux/net/java/games/input/LinuxJoystickAxis.java new file mode 100644 index 0000000..47adc99 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxJoystickAxis.java @@ -0,0 +1,76 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents a linux Axis +* @author elias +* @version 1.0 +*/ +class LinuxJoystickAxis extends AbstractComponent { + private float value; + private boolean analog; + + public LinuxJoystickAxis(Component.Identifier.Axis axis_id) { + this(axis_id, true); + } + + public LinuxJoystickAxis(Component.Identifier.Axis axis_id, boolean analog) { + super(axis_id.getName(), axis_id); + this.analog = analog; + } + + public final boolean isRelative() { + return false; + } + + public final boolean isAnalog() { + return analog; + } + + final void setValue(float value) { + this.value = value; + resetHasPolled(); + } + + protected final float poll() throws IOException { + return value; + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxJoystickButton.java b/src/plugins/linux/net/java/games/input/LinuxJoystickButton.java new file mode 100644 index 0000000..e0a3b3d --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxJoystickButton.java @@ -0,0 +1,65 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents a linux button from the joystick interface +* @author elias +* @version 1.0 +*/ +final class LinuxJoystickButton extends AbstractComponent { + private float value; + + public LinuxJoystickButton(Component.Identifier button_id) { + super(button_id.getName(), button_id); + } + + public final boolean isRelative() { + return false; + } + + final void setValue(float value) { + this.value = value; + } + + protected final float poll() throws IOException { + return value; + } +} diff --git a/src/plugins/linux/net/java/games/input/LinuxJoystickDevice.java b/src/plugins/linux/net/java/games/input/LinuxJoystickDevice.java new file mode 100644 index 0000000..c749684 --- /dev/null +++ b/src/plugins/linux/net/java/games/input/LinuxJoystickDevice.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2003 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; + +/** + * @author elias + */ +final class LinuxJoystickDevice implements LinuxDevice { + public final static int JS_EVENT_BUTTON = 0x01; /* button pressed/released */ + public final static int JS_EVENT_AXIS = 0x02; /* joystick moved */ + public final static int JS_EVENT_INIT = 0x80; /* initial state of device */ + + public final static int AXIS_MAX_VALUE = 32767; + + private final long fd; + private final String name; + + private final LinuxJoystickEvent joystick_event = new LinuxJoystickEvent(); + private final Event event = new Event(); + private final LinuxJoystickButton[] buttons; + private final LinuxJoystickAxis[] axes; + private final Map povXs = new HashMap<>(); + private final Map povYs = new HashMap<>(); + private final byte[] axisMap; + private final char[] buttonMap; + + private EventQueue event_queue; + + /* Closed state variable that protects the validity of the file descriptor. + * Access to the closed state must be synchronized + */ + private boolean closed; + + public LinuxJoystickDevice(String filename) throws IOException { + this.fd = nOpen(filename); + try { + this.name = getDeviceName(); + setBufferSize(AbstractController.EVENT_QUEUE_DEPTH); + buttons = new LinuxJoystickButton[getNumDeviceButtons()]; + axes = new LinuxJoystickAxis[getNumDeviceAxes()]; + axisMap = getDeviceAxisMap(); + buttonMap = getDeviceButtonMap(); + } catch (IOException e) { + close(); + throw e; + } + } + private final static native long nOpen(String filename) throws IOException; + + public final synchronized void setBufferSize(int size) { + event_queue = new EventQueue(size); + } + + private final void processEvent(LinuxJoystickEvent joystick_event) { + int index = joystick_event.getNumber(); + // Filter synthetic init event flag + int type = joystick_event.getType() & ~JS_EVENT_INIT; + switch (type) { + case JS_EVENT_BUTTON: + if (index < getNumButtons()) { + LinuxJoystickButton button = buttons[index]; + if (button != null) { + float value = joystick_event.getValue(); + button.setValue(value); + event.set(button, value, joystick_event.getNanos()); + break; + } + } + return; + case JS_EVENT_AXIS: + if (index < getNumAxes()) { + LinuxJoystickAxis axis = axes[index]; + if (axis != null) { + float value = (float)joystick_event.getValue()/AXIS_MAX_VALUE; + axis.setValue(value); + if(povXs.containsKey(index)) { + LinuxJoystickPOV pov = povXs.get(index); + pov.updateValue(); + event.set(pov, pov.getPollData(), joystick_event.getNanos()); + } else if(povYs.containsKey(index)) { + LinuxJoystickPOV pov = povYs.get(index); + pov.updateValue(); + event.set(pov, pov.getPollData(), joystick_event.getNanos()); + } else { + event.set(axis, value, joystick_event.getNanos()); + } + break; + } + } + return; + default: + // Unknown component type + return; + } + if (!event_queue.isFull()) { + event_queue.add(event); + } + } + + public final void registerAxis(int index, LinuxJoystickAxis axis) { + axes[index] = axis; + } + + public final void registerButton(int index, LinuxJoystickButton button) { + buttons[index] = button; + } + + public final void registerPOV(LinuxJoystickPOV pov) { + // The x and y on a joystick device are not the same as on an event device + LinuxJoystickAxis xAxis = pov.getYAxis(); + LinuxJoystickAxis yAxis = pov.getXAxis(); + int xIndex; + int yIndex; + for(xIndex=0;xIndex map = new HashMap<>(); + + private final int button_id; + + public final static ButtonUsage map(int button_id) { + Integer button_id_obj = button_id; + ButtonUsage existing = map.get(button_id_obj); + if (existing != null) + return existing; + ButtonUsage new_button = new ButtonUsage(button_id); + map.put(button_id_obj, new_button); + return new_button; + } + + private ButtonUsage(int button_id) { + this.button_id = button_id; + } + + public final Component.Identifier.Button getIdentifier() { + switch (button_id) { + case 1: + return Component.Identifier.Button._0; + case 2: + return Component.Identifier.Button._1; + case 3: + return Component.Identifier.Button._2; + case 4: + return Component.Identifier.Button._3; + case 5: + return Component.Identifier.Button._4; + case 6: + return Component.Identifier.Button._5; + case 7: + return Component.Identifier.Button._6; + case 8: + return Component.Identifier.Button._7; + case 9: + return Component.Identifier.Button._8; + case 10: + return Component.Identifier.Button._9; + case 11: + return Component.Identifier.Button._10; + case 12: + return Component.Identifier.Button._11; + case 13: + return Component.Identifier.Button._12; + case 14: + return Component.Identifier.Button._13; + case 15: + return Component.Identifier.Button._14; + case 16: + return Component.Identifier.Button._15; + case 17: + return Component.Identifier.Button._16; + case 18: + return Component.Identifier.Button._17; + case 19: + return Component.Identifier.Button._18; + case 20: + return Component.Identifier.Button._19; + case 21: + return Component.Identifier.Button._20; + case 22: + return Component.Identifier.Button._21; + case 23: + return Component.Identifier.Button._22; + case 24: + return Component.Identifier.Button._23; + case 25: + return Component.Identifier.Button._24; + case 26: + return Component.Identifier.Button._25; + case 27: + return Component.Identifier.Button._26; + case 28: + return Component.Identifier.Button._27; + case 29: + return Component.Identifier.Button._28; + case 30: + return Component.Identifier.Button._29; + case 31: + return Component.Identifier.Button._30; + case 32: + return Component.Identifier.Button._31; + default: + return null; + } + } + + public final String toString() { + return "ButtonUsage (" + button_id + ")"; + } +} diff --git a/src/plugins/osx/net/java/games/input/ElementType.java b/src/plugins/osx/net/java/games/input/ElementType.java new file mode 100644 index 0000000..efc303f --- /dev/null +++ b/src/plugins/osx/net/java/games/input/ElementType.java @@ -0,0 +1,74 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.lang.reflect.Method; + +/** HID Element types +* @author elias +* @version 1.0 +*/ +final class ElementType { + private final static ElementType[] map = new ElementType[514]; + + public final static ElementType INPUT_MISC = new ElementType(1); + public final static ElementType INPUT_BUTTON = new ElementType(2); + public final static ElementType INPUT_AXIS = new ElementType(3); + public final static ElementType INPUT_SCANCODES = new ElementType(4); + public final static ElementType OUTPUT = new ElementType(129); + public final static ElementType FEATURE = new ElementType(257); + public final static ElementType COLLECTION = new ElementType(513); + + private final int type_id; + + public final static ElementType map(int type_id) { + if (type_id < 0 || type_id >= map.length) + return null; + return map[type_id]; + } + + private ElementType(int type_id) { + map[type_id] = this; + this.type_id = type_id; + } + + public final String toString() { + return "ElementType (0x" + Integer.toHexString(type_id) + ")"; + } +} diff --git a/src/plugins/osx/net/java/games/input/GenericDesktopUsage.java b/src/plugins/osx/net/java/games/input/GenericDesktopUsage.java new file mode 100644 index 0000000..4049d68 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/GenericDesktopUsage.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +/** Generic Desktop Usages +* @author elias +* @version 1.0 +*/ +final class GenericDesktopUsage implements Usage { + private final static GenericDesktopUsage[] map = new GenericDesktopUsage[0xFF]; + + public final static GenericDesktopUsage POINTER = new GenericDesktopUsage(0x01); /* Physical Collection */ + public final static GenericDesktopUsage MOUSE = new GenericDesktopUsage(0x02); /* Application Collection */ + /* 0x03 Reserved */ + public final static GenericDesktopUsage JOYSTICK = new GenericDesktopUsage(0x04); /* Application Collection */ + public final static GenericDesktopUsage GAME_PAD = new GenericDesktopUsage(0x05); /* Application Collection */ + public final static GenericDesktopUsage KEYBOARD = new GenericDesktopUsage(0x06); /* Application Collection */ + public final static GenericDesktopUsage KEYPAD = new GenericDesktopUsage(0x07); /* Application Collection */ + public final static GenericDesktopUsage MULTI_AXIS_CONTROLLER = new GenericDesktopUsage(0x08); /* Application Collection */ + /* 0x09 - 0x2F Reserved */ + public final static GenericDesktopUsage X = new GenericDesktopUsage(0x30); /* Dynamic Value */ + public final static GenericDesktopUsage Y = new GenericDesktopUsage(0x31); /* Dynamic Value */ + public final static GenericDesktopUsage Z = new GenericDesktopUsage(0x32); /* Dynamic Value */ + public final static GenericDesktopUsage RX = new GenericDesktopUsage(0x33); /* Dynamic Value */ + public final static GenericDesktopUsage RY = new GenericDesktopUsage(0x34); /* Dynamic Value */ + public final static GenericDesktopUsage RZ = new GenericDesktopUsage(0x35); /* Dynamic Value */ + public final static GenericDesktopUsage SLIDER = new GenericDesktopUsage(0x36); /* Dynamic Value */ + public final static GenericDesktopUsage DIAL = new GenericDesktopUsage(0x37); /* Dynamic Value */ + public final static GenericDesktopUsage WHEEL = new GenericDesktopUsage(0x38); /* Dynamic Value */ + public final static GenericDesktopUsage HATSWITCH = new GenericDesktopUsage(0x39); /* Dynamic Value */ + public final static GenericDesktopUsage COUNTED_BUFFER = new GenericDesktopUsage(0x3A); /* Logical Collection */ + public final static GenericDesktopUsage BYTE_COUNT = new GenericDesktopUsage(0x3B); /* Dynamic Value */ + public final static GenericDesktopUsage MOTION_WAKEUP = new GenericDesktopUsage(0x3C); /* One-Shot Control */ + public final static GenericDesktopUsage START = new GenericDesktopUsage(0x3D); /* On/Off Control */ + public final static GenericDesktopUsage SELECT = new GenericDesktopUsage(0x3E); /* On/Off Control */ + /* 0x3F Reserved */ + public final static GenericDesktopUsage VX = new GenericDesktopUsage(0x40); /* Dynamic Value */ + public final static GenericDesktopUsage VY = new GenericDesktopUsage(0x41); /* Dynamic Value */ + public final static GenericDesktopUsage VZ = new GenericDesktopUsage(0x42); /* Dynamic Value */ + public final static GenericDesktopUsage VBRX = new GenericDesktopUsage(0x43); /* Dynamic Value */ + public final static GenericDesktopUsage VBRY = new GenericDesktopUsage(0x44); /* Dynamic Value */ + public final static GenericDesktopUsage VBRZ = new GenericDesktopUsage(0x45); /* Dynamic Value */ + public final static GenericDesktopUsage VNO = new GenericDesktopUsage(0x46); /* Dynamic Value */ + /* 0x47 - 0x7F Reserved */ + public final static GenericDesktopUsage SYSTEM_CONTROL = new GenericDesktopUsage(0x80); /* Application Collection */ + public final static GenericDesktopUsage SYSTEM_POWER_DOWN = new GenericDesktopUsage(0x81); /* One-Shot Control */ + public final static GenericDesktopUsage SYSTEM_SLEEP = new GenericDesktopUsage(0x82); /* One-Shot Control */ + public final static GenericDesktopUsage SYSTEM_WAKE_UP = new GenericDesktopUsage(0x83); /* One-Shot Control */ + public final static GenericDesktopUsage SYSTEM_CONTEXT_MENU = new GenericDesktopUsage(0x84); /* One-Shot Control */ + public final static GenericDesktopUsage SYSTEM_MAIN_MENU = new GenericDesktopUsage(0x85); /* One-Shot Control */ + public final static GenericDesktopUsage SYSTEM_APP_MENU = new GenericDesktopUsage(0x86); /* One-Shot Control */ + public final static GenericDesktopUsage SYSTEM_MENU_HELP = new GenericDesktopUsage(0x87); /* One-Shot Control */ + public final static GenericDesktopUsage SYSTEM_MENU_EXIT = new GenericDesktopUsage(0x88); /* One-Shot Control */ + public final static GenericDesktopUsage SYSTEM_MENU = new GenericDesktopUsage(0x89); /* Selector */ + public final static GenericDesktopUsage SYSTEM_MENU_RIGHT = new GenericDesktopUsage(0x8A); /* Re-Trigger Control */ + public final static GenericDesktopUsage SYSTEM_MENU_LEFT = new GenericDesktopUsage(0x8B); /* Re-Trigger Control */ + public final static GenericDesktopUsage SYSTEM_MENU_UP = new GenericDesktopUsage(0x8C); /* Re-Trigger Control */ + public final static GenericDesktopUsage SYSTEM_MENU_DOWN = new GenericDesktopUsage(0x8D); /* Re-Trigger Control */ + /* 0x8E - 0x8F Reserved */ + public final static GenericDesktopUsage DPAD_UP = new GenericDesktopUsage(0x90); /* On/Off Control */ + public final static GenericDesktopUsage DPAD_DOWN = new GenericDesktopUsage(0x91); /* On/Off Control */ + public final static GenericDesktopUsage DPAD_RIGHT = new GenericDesktopUsage(0x92); /* On/Off Control */ + public final static GenericDesktopUsage DPAD_LEFT = new GenericDesktopUsage(0x93); /* On/Off Control */ + /* 0x94 - 0xFFFF Reserved */ + + private final int usage_id; + + public final static GenericDesktopUsage map(int usage_id) { + if (usage_id < 0 || usage_id >= map.length) + return null; + return map[usage_id]; + } + + private GenericDesktopUsage(int usage_id) { + map[usage_id] = this; + this.usage_id = usage_id; + } + + public final String toString() { + return "GenericDesktopUsage (0x" + Integer.toHexString(usage_id) + ")"; + } + + public final Component.Identifier getIdentifier() { + if (this == GenericDesktopUsage.X) { + return Component.Identifier.Axis.X; + } else if (this == GenericDesktopUsage.Y) { + return Component.Identifier.Axis.Y; + } else if (this == GenericDesktopUsage.Z || this == GenericDesktopUsage.WHEEL) { + return Component.Identifier.Axis.Z; + } else if (this == GenericDesktopUsage.RX) { + return Component.Identifier.Axis.RX; + } else if (this == GenericDesktopUsage.RY) { + return Component.Identifier.Axis.RY; + } else if (this == GenericDesktopUsage.RZ) { + return Component.Identifier.Axis.RZ; + } else if (this == GenericDesktopUsage.SLIDER) { + return Component.Identifier.Axis.SLIDER; + } else if (this == GenericDesktopUsage.HATSWITCH) { + return Component.Identifier.Axis.POV; + } else if (this == GenericDesktopUsage.SELECT) { + return Component.Identifier.Button.SELECT; + } else + return null; + } + +} diff --git a/src/plugins/osx/net/java/games/input/KeyboardUsage.java b/src/plugins/osx/net/java/games/input/KeyboardUsage.java new file mode 100644 index 0000000..30f2383 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/KeyboardUsage.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +/** Mapping from Keyboard HID usages to Component.Identifier.Key +* @author elias +* @version 1.0 +*/ +final class KeyboardUsage implements Usage { + private final static KeyboardUsage[] map = new KeyboardUsage[0xFF]; + + public static final KeyboardUsage ERRORROLLOVER = new KeyboardUsage(0x01); /* ErrorRollOver */ + public static final KeyboardUsage POSTFAIL = new KeyboardUsage(0x02); /* POSTFail */ + public static final KeyboardUsage ERRORUNDEFINED = new KeyboardUsage(0x03); /* ErrorUndefined */ + public static final KeyboardUsage A = new KeyboardUsage(Component.Identifier.Key.A, 0x04); /* a or A */ + public static final KeyboardUsage B = new KeyboardUsage(Component.Identifier.Key.B, 0x05); /* b or B */ + public static final KeyboardUsage C = new KeyboardUsage(Component.Identifier.Key.C, 0x06); /* c or C */ + public static final KeyboardUsage D = new KeyboardUsage(Component.Identifier.Key.D, 0x07); /* d or D */ + public static final KeyboardUsage E = new KeyboardUsage(Component.Identifier.Key.E, 0x08); /* e or E */ + public static final KeyboardUsage F = new KeyboardUsage(Component.Identifier.Key.F, 0x09); /* f or F */ + public static final KeyboardUsage G = new KeyboardUsage(Component.Identifier.Key.G, 0x0A); /* g or G */ + public static final KeyboardUsage H = new KeyboardUsage(Component.Identifier.Key.H, 0x0B); /* h or H */ + public static final KeyboardUsage I = new KeyboardUsage(Component.Identifier.Key.I, 0x0C); /* i or I */ + public static final KeyboardUsage J = new KeyboardUsage(Component.Identifier.Key.J, 0x0D); /* j or J */ + public static final KeyboardUsage K = new KeyboardUsage(Component.Identifier.Key.K, 0x0E); /* k or K */ + public static final KeyboardUsage L = new KeyboardUsage(Component.Identifier.Key.L, 0x0F); /* l or L */ + public static final KeyboardUsage M = new KeyboardUsage(Component.Identifier.Key.M, 0x10); /* m or M */ + public static final KeyboardUsage N = new KeyboardUsage(Component.Identifier.Key.N, 0x11); /* n or N */ + public static final KeyboardUsage O = new KeyboardUsage(Component.Identifier.Key.O, 0x12); /* o or O */ + public static final KeyboardUsage P = new KeyboardUsage(Component.Identifier.Key.P, 0x13); /* p or P */ + public static final KeyboardUsage Q = new KeyboardUsage(Component.Identifier.Key.Q, 0x14); /* q or Q */ + public static final KeyboardUsage R = new KeyboardUsage(Component.Identifier.Key.R, 0x15); /* r or R */ + public static final KeyboardUsage S = new KeyboardUsage(Component.Identifier.Key.S, 0x16); /* s or S */ + public static final KeyboardUsage T = new KeyboardUsage(Component.Identifier.Key.T, 0x17); /* t or T */ + public static final KeyboardUsage U = new KeyboardUsage(Component.Identifier.Key.U, 0x18); /* u or U */ + public static final KeyboardUsage V = new KeyboardUsage(Component.Identifier.Key.V, 0x19); /* v or V */ + public static final KeyboardUsage W = new KeyboardUsage(Component.Identifier.Key.W, 0x1A); /* w or W */ + public static final KeyboardUsage X = new KeyboardUsage(Component.Identifier.Key.X, 0x1B); /* x or X */ + public static final KeyboardUsage Y = new KeyboardUsage(Component.Identifier.Key.Y, 0x1C); /* y or Y */ + public static final KeyboardUsage Z = new KeyboardUsage(Component.Identifier.Key.Z, 0x1D); /* z or Z */ + public static final KeyboardUsage _1 = new KeyboardUsage(Component.Identifier.Key._1, 0x1E); /* 1 or ! */ + public static final KeyboardUsage _2 = new KeyboardUsage(Component.Identifier.Key._2, 0x1F); /* 2 or @ */ + public static final KeyboardUsage _3 = new KeyboardUsage(Component.Identifier.Key._3, 0x20); /* 3 or # */ + public static final KeyboardUsage _4 = new KeyboardUsage(Component.Identifier.Key._4, 0x21); /* 4 or $ */ + public static final KeyboardUsage _5 = new KeyboardUsage(Component.Identifier.Key._5, 0x22); /* 5 or % */ + public static final KeyboardUsage _6 = new KeyboardUsage(Component.Identifier.Key._6, 0x23); /* 6 or ^ */ + public static final KeyboardUsage _7 = new KeyboardUsage(Component.Identifier.Key._7, 0x24); /* 7 or & */ + public static final KeyboardUsage _8 = new KeyboardUsage(Component.Identifier.Key._8, 0x25); /* 8 or * */ + public static final KeyboardUsage _9 = new KeyboardUsage(Component.Identifier.Key._9, 0x26); /* 9 or ( */ + public static final KeyboardUsage _0 = new KeyboardUsage(Component.Identifier.Key._0, 0x27); /* 0 or ) */ + public static final KeyboardUsage ENTER = new KeyboardUsage(Component.Identifier.Key.RETURN, 0x28); /* Return (Enter) */ + public static final KeyboardUsage ESCAPE = new KeyboardUsage(Component.Identifier.Key.ESCAPE, 0x29); /* Escape */ + public static final KeyboardUsage BACKSPACE = new KeyboardUsage(Component.Identifier.Key.BACK, 0x2A); /* Delete (Backspace) */ + public static final KeyboardUsage TAB = new KeyboardUsage(Component.Identifier.Key.TAB, 0x2B); /* Tab */ + public static final KeyboardUsage SPACEBAR = new KeyboardUsage(Component.Identifier.Key.SPACE, 0x2C); /* Spacebar */ + public static final KeyboardUsage HYPHEN = new KeyboardUsage(Component.Identifier.Key.MINUS, 0x2D); /* - or _ */ + public static final KeyboardUsage EQUALSIGN = new KeyboardUsage(Component.Identifier.Key.EQUALS, 0x2E); /* = or + */ + public static final KeyboardUsage OPENBRACKET = new KeyboardUsage(Component.Identifier.Key.LBRACKET, 0x2F); /* [ or { */ + public static final KeyboardUsage CLOSEBRACKET = new KeyboardUsage(Component.Identifier.Key.RBRACKET, 0x30); /* ] or } */ + public static final KeyboardUsage BACKSLASH = new KeyboardUsage(Component.Identifier.Key.BACKSLASH, 0x31); /* \ or | */ + public static final KeyboardUsage NONUSPOUNT = new KeyboardUsage(Component.Identifier.Key.PERIOD, 0x32); /* Non-US # or _ */ + public static final KeyboardUsage SEMICOLON = new KeyboardUsage(Component.Identifier.Key.SEMICOLON, 0x33); /* ; or : */ + public static final KeyboardUsage QUOTE = new KeyboardUsage(Component.Identifier.Key.APOSTROPHE, 0x34); /* ' or " */ + public static final KeyboardUsage TILDE = new KeyboardUsage(Component.Identifier.Key.GRAVE, 0x35); /* Grave Accent and Tilde */ + public static final KeyboardUsage COMMA = new KeyboardUsage(Component.Identifier.Key.COMMA, 0x36); /* , or < */ + public static final KeyboardUsage PERIOD = new KeyboardUsage(Component.Identifier.Key.PERIOD, 0x37); /* . or > */ + public static final KeyboardUsage SLASH = new KeyboardUsage(Component.Identifier.Key.SLASH, 0x38); /* / or ? */ + public static final KeyboardUsage CAPSLOCK = new KeyboardUsage(Component.Identifier.Key.CAPITAL, 0x39); /* Caps Lock */ + public static final KeyboardUsage F1 = new KeyboardUsage(Component.Identifier.Key.F1, 0x3A); /* F1 */ + public static final KeyboardUsage F2 = new KeyboardUsage(Component.Identifier.Key.F2, 0x3B); /* F2 */ + public static final KeyboardUsage F3 = new KeyboardUsage(Component.Identifier.Key.F3, 0x3C); /* F3 */ + public static final KeyboardUsage F4 = new KeyboardUsage(Component.Identifier.Key.F4, 0x3D); /* F4 */ + public static final KeyboardUsage F5 = new KeyboardUsage(Component.Identifier.Key.F5, 0x3E); /* F5 */ + public static final KeyboardUsage F6 = new KeyboardUsage(Component.Identifier.Key.F6, 0x3F); /* F6 */ + public static final KeyboardUsage F7 = new KeyboardUsage(Component.Identifier.Key.F7, 0x40); /* F7 */ + public static final KeyboardUsage F8 = new KeyboardUsage(Component.Identifier.Key.F8, 0x41); /* F8 */ + public static final KeyboardUsage F9 = new KeyboardUsage(Component.Identifier.Key.F9, 0x42); /* F9 */ + public static final KeyboardUsage F10 = new KeyboardUsage(Component.Identifier.Key.F10, 0x43); /* F10 */ + public static final KeyboardUsage F11 = new KeyboardUsage(Component.Identifier.Key.F11, 0x44); /* F11 */ + public static final KeyboardUsage F12 = new KeyboardUsage(Component.Identifier.Key.F12, 0x45); /* F12 */ + public static final KeyboardUsage PRINTSCREEN = new KeyboardUsage(Component.Identifier.Key.SYSRQ, 0x46); /* PrintScreen */ + public static final KeyboardUsage SCROLLLOCK = new KeyboardUsage(Component.Identifier.Key.SCROLL, 0x47); /* Scroll Lock */ + public static final KeyboardUsage PAUSE = new KeyboardUsage(Component.Identifier.Key.PAUSE, 0x48); /* Pause */ + public static final KeyboardUsage INSERT = new KeyboardUsage(Component.Identifier.Key.INSERT, 0x49); /* Insert */ + public static final KeyboardUsage HOME = new KeyboardUsage(Component.Identifier.Key.HOME, 0x4A); /* Home */ + public static final KeyboardUsage PAGEUP = new KeyboardUsage(Component.Identifier.Key.PAGEUP, 0x4B); /* Page Up */ + public static final KeyboardUsage DELETE = new KeyboardUsage(Component.Identifier.Key.DELETE, 0x4C); /* Delete Forward */ + public static final KeyboardUsage END = new KeyboardUsage(Component.Identifier.Key.END, 0x4D); /* End */ + public static final KeyboardUsage PAGEDOWN = new KeyboardUsage(Component.Identifier.Key.PAGEDOWN, 0x4E); /* Page Down */ + public static final KeyboardUsage RIGHTARROW = new KeyboardUsage(Component.Identifier.Key.RIGHT, 0x4F); /* Right Arrow */ + public static final KeyboardUsage LEFTARROW = new KeyboardUsage(Component.Identifier.Key.LEFT, 0x50); /* Left Arrow */ + public static final KeyboardUsage DOWNARROW = new KeyboardUsage(Component.Identifier.Key.DOWN, 0x51); /* Down Arrow */ + public static final KeyboardUsage UPARROW = new KeyboardUsage(Component.Identifier.Key.UP, 0x52); /* Up Arrow */ + public static final KeyboardUsage KEYPAD_NUMLOCK = new KeyboardUsage(Component.Identifier.Key.NUMLOCK, 0x53); /* Keypad NumLock or Clear */ + public static final KeyboardUsage KEYPAD_SLASH = new KeyboardUsage(Component.Identifier.Key.DIVIDE, 0x54); /* Keypad / */ + public static final KeyboardUsage KEYPAD_ASTERICK = new KeyboardUsage(0x55); /* Keypad * */ + public static final KeyboardUsage KEYPAD_HYPHEN = new KeyboardUsage(Component.Identifier.Key.SUBTRACT, 0x56); /* Keypad - */ + public static final KeyboardUsage KEYPAD_PLUS = new KeyboardUsage(Component.Identifier.Key.ADD, 0x57); /* Keypad + */ + public static final KeyboardUsage KEYPAD_ENTER = new KeyboardUsage(Component.Identifier.Key.NUMPADENTER, 0x58); /* Keypad Enter */ + public static final KeyboardUsage KEYPAD_1 = new KeyboardUsage(Component.Identifier.Key.NUMPAD1, 0x59); /* Keypad 1 or End */ + public static final KeyboardUsage KEYPAD_2 = new KeyboardUsage(Component.Identifier.Key.NUMPAD2, 0x5A); /* Keypad 2 or Down Arrow */ + public static final KeyboardUsage KEYPAD_3 = new KeyboardUsage(Component.Identifier.Key.NUMPAD3, 0x5B); /* Keypad 3 or Page Down */ + public static final KeyboardUsage KEYPAD_4 = new KeyboardUsage(Component.Identifier.Key.NUMPAD4, 0x5C); /* Keypad 4 or Left Arrow */ + public static final KeyboardUsage KEYPAD_5 = new KeyboardUsage(Component.Identifier.Key.NUMPAD5, 0x5D); /* Keypad 5 */ + public static final KeyboardUsage KEYPAD_6 = new KeyboardUsage(Component.Identifier.Key.NUMPAD6, 0x5E); /* Keypad 6 or Right Arrow */ + public static final KeyboardUsage KEYPAD_7 = new KeyboardUsage(Component.Identifier.Key.NUMPAD7, 0x5F); /* Keypad 7 or Home */ + public static final KeyboardUsage KEYPAD_8 = new KeyboardUsage(Component.Identifier.Key.NUMPAD8, 0x60); /* Keypad 8 or Up Arrow */ + public static final KeyboardUsage KEYPAD_9 = new KeyboardUsage(Component.Identifier.Key.NUMPAD9, 0x61); /* Keypad 9 or Page Up */ + public static final KeyboardUsage KEYPAD_0 = new KeyboardUsage(Component.Identifier.Key.NUMPAD0, 0x62); /* Keypad 0 or Insert */ + public static final KeyboardUsage KEYPAD_PERIOD = new KeyboardUsage(Component.Identifier.Key.DECIMAL, 0x63); /* Keypad . or Delete */ + public static final KeyboardUsage NONUSBACKSLASH = new KeyboardUsage(Component.Identifier.Key.BACKSLASH, 0x64); /* Non-US \ or | */ + public static final KeyboardUsage APPLICATION = new KeyboardUsage(Component.Identifier.Key.APPS, 0x65); /* Application */ + public static final KeyboardUsage POWER = new KeyboardUsage(Component.Identifier.Key.POWER, 0x66); /* Power */ + public static final KeyboardUsage KEYPAD_EQUALSIGN = new KeyboardUsage(Component.Identifier.Key.NUMPADEQUAL, 0x67); /* Keypad = */ + public static final KeyboardUsage F13 = new KeyboardUsage(Component.Identifier.Key.F13, 0x68); /* F13 */ + public static final KeyboardUsage F14 = new KeyboardUsage(Component.Identifier.Key.F14, 0x69); /* F14 */ + public static final KeyboardUsage F15 = new KeyboardUsage(Component.Identifier.Key.F15, 0x6A); /* F15 */ + public static final KeyboardUsage F16 = new KeyboardUsage(0x6B); /* F16 */ + public static final KeyboardUsage F17 = new KeyboardUsage(0x6C); /* F17 */ + public static final KeyboardUsage F18 = new KeyboardUsage(0x6D); /* F18 */ + public static final KeyboardUsage F19 = new KeyboardUsage(0x6E); /* F19 */ + public static final KeyboardUsage F20 = new KeyboardUsage(0x6F); /* F20 */ + public static final KeyboardUsage F21 = new KeyboardUsage(0x70); /* F21 */ + public static final KeyboardUsage F22 = new KeyboardUsage(0x71); /* F22 */ + public static final KeyboardUsage F23 = new KeyboardUsage(0x72); /* F23 */ + public static final KeyboardUsage F24 = new KeyboardUsage(0x73); /* F24 */ + public static final KeyboardUsage EXECUTE = new KeyboardUsage(0x74); /* Execute */ + public static final KeyboardUsage HELP = new KeyboardUsage(0x75); /* Help */ + public static final KeyboardUsage MENU = new KeyboardUsage(0x76); /* Menu */ + public static final KeyboardUsage SELECT = new KeyboardUsage(0x77); /* Select */ + public static final KeyboardUsage STOP = new KeyboardUsage(Component.Identifier.Key.STOP, 0x78); /* Stop */ + public static final KeyboardUsage AGAIN = new KeyboardUsage(0x79); /* Again */ + public static final KeyboardUsage UNDO = new KeyboardUsage(0x7A); /* Undo */ + public static final KeyboardUsage CUT = new KeyboardUsage(0x7B); /* Cut */ + public static final KeyboardUsage COPY = new KeyboardUsage(0x7C); /* Copy */ + public static final KeyboardUsage PASTE = new KeyboardUsage(0x7D); /* Paste */ + public static final KeyboardUsage FIND = new KeyboardUsage(0x7E); /* Find */ + public static final KeyboardUsage MUTE = new KeyboardUsage(0x7F); /* Mute */ + public static final KeyboardUsage VOLUMEUP = new KeyboardUsage(0x80); /* Volume Up */ + public static final KeyboardUsage VOLUMEDOWN = new KeyboardUsage(0x81); /* Volume Down */ + public static final KeyboardUsage LOCKINGCAPSLOCK = new KeyboardUsage(Component.Identifier.Key.CAPITAL, 0x82); /* Locking Caps Lock */ + public static final KeyboardUsage LOCKINGNUMLOCK = new KeyboardUsage(Component.Identifier.Key.NUMLOCK, 0x83); /* Locking Num Lock */ + public static final KeyboardUsage LOCKINGSCROLLLOCK = new KeyboardUsage(Component.Identifier.Key.SCROLL, 0x84); /* Locking Scroll Lock */ + public static final KeyboardUsage KEYPAD_COMMA = new KeyboardUsage(Component.Identifier.Key.COMMA, 0x85); /* Keypad Comma */ + public static final KeyboardUsage KEYPAD_EQUALSSIGNAS400 = new KeyboardUsage(0x86); /* Keypad Equal Sign for AS/400 */ + public static final KeyboardUsage INTERNATIONAL1 = new KeyboardUsage(0x87); /* International1 */ + public static final KeyboardUsage INTERNATIONAL2 = new KeyboardUsage(0x88); /* International2 */ + public static final KeyboardUsage INTERNATIONAL3 = new KeyboardUsage(0x89); /* International3 */ + public static final KeyboardUsage INTERNATIONAL4 = new KeyboardUsage(0x8A); /* International4 */ + public static final KeyboardUsage INTERNATIONAL5 = new KeyboardUsage(0x8B); /* International5 */ + public static final KeyboardUsage INTERNATIONAL6 = new KeyboardUsage(0x8C); /* International6 */ + public static final KeyboardUsage INTERNATIONAL7 = new KeyboardUsage(0x8D); /* International7 */ + public static final KeyboardUsage INTERNATIONAL8 = new KeyboardUsage(0x8E); /* International8 */ + public static final KeyboardUsage INTERNATIONAL9 = new KeyboardUsage(0x8F); /* International9 */ + public static final KeyboardUsage LANG1 = new KeyboardUsage(0x90); /* LANG1 */ + public static final KeyboardUsage LANG2 = new KeyboardUsage(0x91); /* LANG2 */ + public static final KeyboardUsage LANG3 = new KeyboardUsage(0x92); /* LANG3 */ + public static final KeyboardUsage LANG4 = new KeyboardUsage(0x93); /* LANG4 */ + public static final KeyboardUsage LANG5 = new KeyboardUsage(0x94); /* LANG5 */ + public static final KeyboardUsage LANG6 = new KeyboardUsage(0x95); /* LANG6 */ + public static final KeyboardUsage LANG7 = new KeyboardUsage(0x96); /* LANG7 */ + public static final KeyboardUsage LANG8 = new KeyboardUsage(0x97); /* LANG8 */ + public static final KeyboardUsage LANG9 = new KeyboardUsage(0x98); /* LANG9 */ + public static final KeyboardUsage ALTERNATEERASE = new KeyboardUsage(0x99); /* AlternateErase */ + public static final KeyboardUsage SYSREQORATTENTION = new KeyboardUsage(Component.Identifier.Key.SYSRQ, 0x9A); /* SysReq/Attention */ + public static final KeyboardUsage CANCEL = new KeyboardUsage(0x9B); /* Cancel */ + public static final KeyboardUsage CLEAR = new KeyboardUsage(0x9C); /* Clear */ + public static final KeyboardUsage PRIOR = new KeyboardUsage(Component.Identifier.Key.PAGEUP, 0x9D); /* Prior */ + public static final KeyboardUsage RETURN = new KeyboardUsage(Component.Identifier.Key.RETURN, 0x9E); /* Return */ + public static final KeyboardUsage SEPARATOR = new KeyboardUsage(0x9F); /* Separator */ + public static final KeyboardUsage OUT = new KeyboardUsage(0xA0); /* Out */ + public static final KeyboardUsage OPER = new KeyboardUsage(0xA1); /* Oper */ + public static final KeyboardUsage CLEARORAGAIN = new KeyboardUsage(0xA2); /* Clear/Again */ + public static final KeyboardUsage CRSELORPROPS = new KeyboardUsage(0xA3); /* CrSel/Props */ + public static final KeyboardUsage EXSEL = new KeyboardUsage(0xA4); /* ExSel */ + /* 0xA5-0xDF Reserved */ + public static final KeyboardUsage LEFTCONTROL = new KeyboardUsage(Component.Identifier.Key.LCONTROL, 0xE0); /* Left Control */ + public static final KeyboardUsage LEFTSHIFT = new KeyboardUsage(Component.Identifier.Key.LSHIFT, 0xE1); /* Left Shift */ + public static final KeyboardUsage LEFTALT = new KeyboardUsage(Component.Identifier.Key.LALT, 0xE2); /* Left Alt */ + public static final KeyboardUsage LEFTGUI = new KeyboardUsage(Component.Identifier.Key.LWIN, 0xE3); /* Left GUI */ + public static final KeyboardUsage RIGHTCONTROL = new KeyboardUsage(Component.Identifier.Key.RCONTROL, 0xE4); /* Right Control */ + public static final KeyboardUsage RIGHTSHIFT = new KeyboardUsage(Component.Identifier.Key.RSHIFT, 0xE5); /* Right Shift */ + public static final KeyboardUsage RIGHTALT = new KeyboardUsage(Component.Identifier.Key.RALT, 0xE6); /* Right Alt */ + public static final KeyboardUsage RIGHTGUI = new KeyboardUsage(Component.Identifier.Key.RWIN, 0xE7); /* Right GUI */ + + private final int usage; + private final Component.Identifier.Key identifier; + + public final Component.Identifier.Key getIdentifier() { + return identifier; + } + + public final static KeyboardUsage map(int usage) { + if (usage < 0 || usage >= map.length) + return null; + return map[usage]; + } + + private KeyboardUsage(int usage) { + this(Component.Identifier.Key.UNKNOWN, usage); + } + + private KeyboardUsage(Component.Identifier.Key id, int usage) { + this.identifier = id; + this.usage = usage; + map[usage] = this; + } + + public final String toString() { + return "KeyboardUsage (0x" + Integer.toHexString(usage) + ")"; + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXAbstractController.java b/src/plugins/osx/net/java/games/input/OSXAbstractController.java new file mode 100644 index 0000000..4248c88 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXAbstractController.java @@ -0,0 +1,74 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents an OSX AbstractController +* @author elias +* @version 1.0 +*/ +final class OSXAbstractController extends AbstractController { + private final PortType port; + private final OSXHIDQueue queue; + private final Type type; + + protected OSXAbstractController(OSXHIDDevice device, OSXHIDQueue queue, Component[] components, Controller[] children, Rumbler[] rumblers, Type type) { + super(device.getProductName(), components, children, rumblers); + this.queue = queue; + this.type = type; + this.port = device.getPortType(); + } + + protected final boolean getNextDeviceEvent(Event event) throws IOException { + return OSXControllers.getNextDeviceEvent(event, queue); + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + queue.setQueueDepth(size); + } + + public Type getType() { + return type; + } + + public final PortType getPortType() { + return port; + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXComponent.java b/src/plugins/osx/net/java/games/input/OSXComponent.java new file mode 100644 index 0000000..6533a79 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXComponent.java @@ -0,0 +1,70 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents an OSX Component +* @author elias +* @version 1.0 +*/ +class OSXComponent extends AbstractComponent { + private final OSXHIDElement element; + + public OSXComponent(Component.Identifier id, OSXHIDElement element) { + super(id.getName(), id); + this.element = element; + } + + public final boolean isRelative() { + return element.isRelative(); + } + + public boolean isAnalog() { + return element.isAnalog(); + } + + public final OSXHIDElement getElement() { + return element; + } + + protected float poll() throws IOException { + return OSXControllers.poll(element); + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXControllers.java b/src/plugins/osx/net/java/games/input/OSXControllers.java new file mode 100644 index 0000000..704ccc5 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXControllers.java @@ -0,0 +1,64 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** helper methods for OSX specific Controllers +* @author elias +* @version 1.0 +*/ +final class OSXControllers { + private final static OSXEvent osx_event = new OSXEvent(); + + public final static synchronized float poll(OSXHIDElement element) throws IOException { + element.getElementValue(osx_event); + return element.convertValue(osx_event.getValue()); + } + + /* synchronized to protect osx_event */ + public final static synchronized boolean getNextDeviceEvent(Event event, OSXHIDQueue queue) throws IOException { + if (queue.getNextEvent(osx_event)) { + OSXComponent component = queue.mapEvent(osx_event); + event.set(component, component.getElement().convertValue(osx_event.getValue()), osx_event.getNanos()); + return true; + } else + return false; + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXEnvironmentPlugin.java b/src/plugins/osx/net/java/games/input/OSXEnvironmentPlugin.java new file mode 100644 index 0000000..2278313 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXEnvironmentPlugin.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.StringTokenizer; + +import net.java.games.util.plugins.Plugin; +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** OSX HIDManager implementation +* @author elias +* @author gregorypierce +* @version 1.0 +*/ +public final class OSXEnvironmentPlugin extends ControllerEnvironment implements Plugin { + + private static boolean supported = false; + + /** + * Static utility method for loading native libraries. + * It will try to load from either the path given by + * the net.java.games.input.librarypath property + * or through System.loadLibrary(). + * + */ + static void loadLibrary(final String lib_name) { + AccessController.doPrivileged((PrivilegedAction) () -> { + try { + String lib_path = System.getProperty("net.java.games.input.librarypath"); + if (lib_path != null) + System.load(lib_path + File.separator + System.mapLibraryName(lib_name)); + else + System.loadLibrary(lib_name); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + supported = false; + } + return null; + }); + } + + static String getPrivilegedProperty(final String property) { + return AccessController.doPrivileged((PrivilegedAction)() -> System.getProperty(property)); + } + + + static String getPrivilegedProperty(final String property, final String default_value) { + return AccessController.doPrivileged((PrivilegedAction)() -> System.getProperty(property, default_value)); + } + + static { + String osName = getPrivilegedProperty("os.name", "").trim(); + if(osName.equals("Mac OS X")) { + // Could check isMacOSXEqualsOrBetterThan in here too. + supported = true; + loadLibrary("jinput-osx"); + } + } + + private final static boolean isMacOSXEqualsOrBetterThan(int major_required, int minor_required) { + String os_version = System.getProperty("os.version"); + StringTokenizer version_tokenizer = new StringTokenizer(os_version, "."); + int major; + int minor; + try { + String major_str = version_tokenizer.nextToken(); + String minor_str = version_tokenizer.nextToken(); + major = Integer.parseInt(major_str); + minor = Integer.parseInt(minor_str); + } catch (Exception e) { + log("Exception occurred while trying to determine OS version: " + e); + // Best guess, no + return false; + } + return major > major_required || (major == major_required && minor >= minor_required); + } + + private final Controller[] controllers; + + public OSXEnvironmentPlugin() { + if(isSupported()) { + this.controllers = enumerateControllers(); + } else { + this.controllers = new Controller[0]; + } + } + + public final Controller[] getControllers() { + return controllers; + } + + public boolean isSupported() { + return supported; + } + + private final static void addElements(OSXHIDQueue queue, List elements, List components, boolean map_mouse_buttons) throws IOException { + Iterator it = elements.iterator(); + while (it.hasNext()) { + OSXHIDElement element = it.next(); + Component.Identifier id = element.getIdentifier(); + if (id == null) + continue; + if (map_mouse_buttons) { + if (id == Component.Identifier.Button._0) { + id = Component.Identifier.Button.LEFT; + } else if (id == Component.Identifier.Button._1) { + id = Component.Identifier.Button.RIGHT; + } else if (id == Component.Identifier.Button._2) { + id = Component.Identifier.Button.MIDDLE; + } + } + OSXComponent component = new OSXComponent(id, element); + components.add(component); + queue.addElement(element, component); + } + } + + private final static Keyboard createKeyboardFromDevice(OSXHIDDevice device, List elements) throws IOException { + List components = new ArrayList<>(); + OSXHIDQueue queue = device.createQueue(AbstractController.EVENT_QUEUE_DEPTH); + try { + addElements(queue, elements, components, false); + } catch (IOException e) { + queue.release(); + throw e; + } + Component[] components_array = new Component[components.size()]; + components.toArray(components_array); + return new OSXKeyboard(device, queue, components_array, new Controller[]{}, new Rumbler[]{}); + } + + private final static Mouse createMouseFromDevice(OSXHIDDevice device, List elements) throws IOException { + List components = new ArrayList<>(); + OSXHIDQueue queue = device.createQueue(AbstractController.EVENT_QUEUE_DEPTH); + try { + addElements(queue, elements, components, true); + } catch (IOException e) { + queue.release(); + throw e; + } + Component[] components_array = new Component[components.size()]; + components.toArray(components_array); + Mouse mouse = new OSXMouse(device, queue, components_array, new Controller[]{}, new Rumbler[]{}); + if (mouse.getPrimaryButton() != null && mouse.getX() != null && mouse.getY() != null) { + return mouse; + } else { + queue.release(); + return null; + } + } + + private final static AbstractController createControllerFromDevice(OSXHIDDevice device, List elements, Controller.Type type) throws IOException { + List components = new ArrayList<>(); + OSXHIDQueue queue = device.createQueue(AbstractController.EVENT_QUEUE_DEPTH); + try { + addElements(queue, elements, components, false); + } catch (IOException e) { + queue.release(); + throw e; + } + Component[] components_array = new Component[components.size()]; + components.toArray(components_array); + return new OSXAbstractController(device, queue, components_array, new Controller[]{}, new Rumbler[]{}, type); + } + + private final static void createControllersFromDevice(OSXHIDDevice device, List controllers) throws IOException { + UsagePair usage_pair = device.getUsagePair(); + if (usage_pair == null) + return; + List elements = device.getElements(); + if (usage_pair.getUsagePage() == UsagePage.GENERIC_DESKTOP && (usage_pair.getUsage() == GenericDesktopUsage.MOUSE || + usage_pair.getUsage() == GenericDesktopUsage.POINTER)) { + Controller mouse = createMouseFromDevice(device, elements); + if (mouse != null) + controllers.add(mouse); + } else if (usage_pair.getUsagePage() == UsagePage.GENERIC_DESKTOP && (usage_pair.getUsage() == GenericDesktopUsage.KEYBOARD || + usage_pair.getUsage() == GenericDesktopUsage.KEYPAD)) { + controllers.add(createKeyboardFromDevice(device, elements)); + } else if (usage_pair.getUsagePage() == UsagePage.GENERIC_DESKTOP && usage_pair.getUsage() == GenericDesktopUsage.JOYSTICK) { + controllers.add(createControllerFromDevice(device, elements, Controller.Type.STICK)); + } else if (usage_pair.getUsagePage() == UsagePage.GENERIC_DESKTOP && usage_pair.getUsage() == GenericDesktopUsage.MULTI_AXIS_CONTROLLER) { + controllers.add(createControllerFromDevice(device, elements, Controller.Type.STICK)); + } else if (usage_pair.getUsagePage() == UsagePage.GENERIC_DESKTOP && usage_pair.getUsage() == GenericDesktopUsage.GAME_PAD) { + controllers.add(createControllerFromDevice(device, elements, Controller.Type.GAMEPAD)); + } + } + + private final static Controller[] enumerateControllers() { + List controllers = new ArrayList<>(); + try { + OSXHIDDeviceIterator it = new OSXHIDDeviceIterator(); + try { + while (true) { + OSXHIDDevice device; + try { + device = it.next(); + if (device == null) + break; + boolean device_used = false; + try { + int old_size = controllers.size(); + createControllersFromDevice(device, controllers); + device_used = old_size != controllers.size(); + } catch (IOException e) { + log("Failed to create controllers from device: " + device.getProductName()); + } + if (!device_used) + device.release(); + } catch (IOException e) { + log("Failed to enumerate device: " + e.getMessage()); + } + } + } finally { + it.close(); + } + } catch (IOException e) { + log("Failed to enumerate devices: " + e.getMessage()); + return new Controller[]{}; + } + Controller[] controllers_array = new Controller[controllers.size()]; + controllers.toArray(controllers_array); + return controllers_array; + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXEvent.java b/src/plugins/osx/net/java/games/input/OSXEvent.java new file mode 100644 index 0000000..9db46e6 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXEvent.java @@ -0,0 +1,73 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +/** OSX Event structure corresponding to IOHIDEventStruct +* @author elias +* @version 1.0 +*/ +class OSXEvent { + private long type; + private long cookie; + private int value; + private long nanos; + + public void set(long type, long cookie, int value, long nanos) { + this.type = type; + this.cookie = cookie; + this.value = value; + this.nanos = nanos; + } + + public long getType() { + return type; + } + + public long getCookie() { + return cookie; + } + + public int getValue() { + return value; + } + + public long getNanos() { + return nanos; + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXHIDDevice.java b/src/plugins/osx/net/java/games/input/OSXHIDDevice.java new file mode 100644 index 0000000..7ac4831 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXHIDDevice.java @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.Iterator; +import java.util.logging.Logger; + +/** OSX HIDManager implementation +* @author elias +* @author gregorypierce +* @version 1.0 +*/ +final class OSXHIDDevice { + private final static Logger log = Logger.getLogger(OSXHIDDevice.class.getName()); + private final static int AXIS_DEFAULT_MIN_VALUE = 0; + private final static int AXIS_DEFAULT_MAX_VALUE = 64*1024; + + private final static String kIOHIDTransportKey = "Transport"; + private final static String kIOHIDVendorIDKey = "VendorID"; + private final static String kIOHIDVendorIDSourceKey = "VendorIDSource"; + private final static String kIOHIDProductIDKey = "ProductID"; + private final static String kIOHIDVersionNumberKey = "VersionNumber"; + private final static String kIOHIDManufacturerKey = "Manufacturer"; + private final static String kIOHIDProductKey = "Product"; + private final static String kIOHIDSerialNumberKey = "SerialNumber"; + private final static String kIOHIDCountryCodeKey = "CountryCode"; + private final static String kIOHIDLocationIDKey = "LocationID"; + private final static String kIOHIDDeviceUsageKey = "DeviceUsage"; + private final static String kIOHIDDeviceUsagePageKey = "DeviceUsagePage"; + private final static String kIOHIDDeviceUsagePairsKey = "DeviceUsagePairs"; + private final static String kIOHIDPrimaryUsageKey = "PrimaryUsage"; + private final static String kIOHIDPrimaryUsagePageKey = "PrimaryUsagePage"; + private final static String kIOHIDMaxInputReportSizeKey = "MaxInputReportSize"; + private final static String kIOHIDMaxOutputReportSizeKey = "MaxOutputReportSize"; + private final static String kIOHIDMaxFeatureReportSizeKey = "MaxFeatureReportSize"; + + private final static String kIOHIDElementKey = "Elements"; + + private final static String kIOHIDElementCookieKey = "ElementCookie"; + private final static String kIOHIDElementTypeKey = "Type"; + private final static String kIOHIDElementCollectionTypeKey = "CollectionType"; + private final static String kIOHIDElementUsageKey = "Usage"; + private final static String kIOHIDElementUsagePageKey = "UsagePage"; + private final static String kIOHIDElementMinKey = "Min"; + private final static String kIOHIDElementMaxKey = "Max"; + private final static String kIOHIDElementScaledMinKey = "ScaledMin"; + private final static String kIOHIDElementScaledMaxKey = "ScaledMax"; + private final static String kIOHIDElementSizeKey = "Size"; + private final static String kIOHIDElementReportSizeKey = "ReportSize"; + private final static String kIOHIDElementReportCountKey = "ReportCount"; + private final static String kIOHIDElementReportIDKey = "ReportID"; + private final static String kIOHIDElementIsArrayKey = "IsArray"; + private final static String kIOHIDElementIsRelativeKey = "IsRelative"; + private final static String kIOHIDElementIsWrappingKey = "IsWrapping"; + private final static String kIOHIDElementIsNonLinearKey = "IsNonLinear"; + private final static String kIOHIDElementHasPreferredStateKey = "HasPreferredState"; + private final static String kIOHIDElementHasNullStateKey = "HasNullState"; + private final static String kIOHIDElementUnitKey = "Unit"; + private final static String kIOHIDElementUnitExponentKey = "UnitExponent"; + private final static String kIOHIDElementNameKey = "Name"; + private final static String kIOHIDElementValueLocationKey = "ValueLocation"; + private final static String kIOHIDElementDuplicateIndexKey = "DuplicateIndex"; + private final static String kIOHIDElementParentCollectionKey = "ParentCollection"; + + private final long device_address; + private final long device_interface_address; + private final Map properties; + + private boolean released; + + public OSXHIDDevice(long device_address, long device_interface_address) throws IOException { + this.device_address = device_address; + this.device_interface_address = device_interface_address; + this.properties = getDeviceProperties(); + open(); + } + + public final Controller.PortType getPortType() { + String transport = (String)properties.get(kIOHIDTransportKey); + if (transport == null) + return Controller.PortType.UNKNOWN; + if (transport.equals("USB")) { + return Controller.PortType.USB; + } else { + return Controller.PortType.UNKNOWN; + } + } + + public final String getProductName() { + return (String)properties.get(kIOHIDProductKey); + } + + private final OSXHIDElement createElementFromElementProperties(Map element_properties) { + /* long size = getLongFromProperties(element_properties, kIOHIDElementSizeKey); + // ignore elements that can't fit into the 32 bit value field of a hid event + if (size > 32) + return null;*/ + long element_cookie = getLongFromProperties(element_properties, kIOHIDElementCookieKey); + int element_type_id = getIntFromProperties(element_properties, kIOHIDElementTypeKey); + ElementType element_type = ElementType.map(element_type_id); + int min = (int)getLongFromProperties(element_properties, kIOHIDElementMinKey, AXIS_DEFAULT_MIN_VALUE); + int max = (int)getLongFromProperties(element_properties, kIOHIDElementMaxKey, AXIS_DEFAULT_MAX_VALUE); +/* long scaled_min = getLongFromProperties(element_properties, kIOHIDElementScaledMinKey, Long.MIN_VALUE); + long scaled_max = getLongFromProperties(element_properties, kIOHIDElementScaledMaxKey, Long.MAX_VALUE);*/ + UsagePair device_usage_pair = getUsagePair(); + boolean default_relative = device_usage_pair != null && (device_usage_pair.getUsage() == GenericDesktopUsage.POINTER || device_usage_pair.getUsage() == GenericDesktopUsage.MOUSE); + + boolean is_relative = getBooleanFromProperties(element_properties, kIOHIDElementIsRelativeKey, default_relative); +/* boolean is_wrapping = getBooleanFromProperties(element_properties, kIOHIDElementIsWrappingKey); + boolean is_non_linear = getBooleanFromProperties(element_properties, kIOHIDElementIsNonLinearKey); + boolean has_preferred_state = getBooleanFromProperties(element_properties, kIOHIDElementHasPreferredStateKey); + boolean has_null_state = getBooleanFromProperties(element_properties, kIOHIDElementHasNullStateKey);*/ + int usage = getIntFromProperties(element_properties, kIOHIDElementUsageKey); + int usage_page = getIntFromProperties(element_properties, kIOHIDElementUsagePageKey); + UsagePair usage_pair = createUsagePair(usage_page, usage); + if (usage_pair == null || (element_type != ElementType.INPUT_MISC && element_type != ElementType.INPUT_BUTTON && element_type != ElementType.INPUT_AXIS)) { + //log.info("element_type = 0x" + element_type + " | usage = " + usage + " | usage_page = " + usage_page); + return null; + } else { + return new OSXHIDElement(this, usage_pair, element_cookie, element_type, min, max, is_relative); + } + } + + @SuppressWarnings("unchecked") + private final void addElements(List elements, Map properties) { + Object[] elements_properties = (Object[])properties.get(kIOHIDElementKey); + if (elements_properties == null) + return; + for (int i = 0; i < elements_properties.length; i++) { + Map element_properties = (Map)elements_properties[i]; + OSXHIDElement element = createElementFromElementProperties(element_properties); + if (element != null) { + elements.add(element); + } + addElements(elements, element_properties); + } + } + + public final List getElements() { + List elements = new ArrayList<>(); + addElements(elements, properties); + return elements; + } + + private final static long getLongFromProperties(Map properties, String key, long default_value) { + Long long_obj = (Long)properties.get(key); + if (long_obj == null) + return default_value; + return long_obj.longValue(); + } + + private final static boolean getBooleanFromProperties(Map properties, String key, boolean default_value) { + return getLongFromProperties(properties, key, default_value ? 1 : 0) != 0; + } + + private final static int getIntFromProperties(Map properties, String key) { + return (int)getLongFromProperties(properties, key); + } + + private final static long getLongFromProperties(Map properties, String key) { + Long long_obj = (Long)properties.get(key); + return long_obj.longValue(); + } + + private final static UsagePair createUsagePair(int usage_page_id, int usage_id) { + UsagePage usage_page = UsagePage.map(usage_page_id); + if (usage_page != null) { + Usage usage = usage_page.mapUsage(usage_id); + if (usage != null) + return new UsagePair(usage_page, usage); + } + return null; + } + + public final UsagePair getUsagePair() { + int usage_page_id = getIntFromProperties(properties, kIOHIDPrimaryUsagePageKey); + int usage_id = getIntFromProperties(properties, kIOHIDPrimaryUsageKey); + return createUsagePair(usage_page_id, usage_id); + } + + private final void dumpProperties() { + log.info(toString()); + dumpMap("", properties); + } + + private final static void dumpArray(String prefix, Object[] array) { + log.info(prefix + "{"); + for (int i = 0; i < array.length; i++) { + dumpObject(prefix + "\t", array[i]); + log.info(prefix + ","); + } + log.info(prefix + "}"); + } + + private final static void dumpMap(String prefix, Map map) { + Iterator keys = map.keySet().iterator(); + while (keys.hasNext()) { + Object key = keys.next(); + Object value = map.get(key); + dumpObject(prefix, key); + dumpObject(prefix + "\t", value); + } + } + + @SuppressWarnings("unchecked") + private final static void dumpObject(String prefix, Object obj) { + if (obj instanceof Long) { + Long l = (Long)obj; + log.info(prefix + "0x" + Long.toHexString(l.longValue())); + } else if (obj instanceof Map) + dumpMap(prefix, (Map)obj); + else if (obj.getClass().isArray()) + dumpArray(prefix, (Object[])obj); + else + log.info(prefix + obj); + } + + private final Map getDeviceProperties() throws IOException { + return nGetDeviceProperties(device_address); + } + private final static native Map nGetDeviceProperties(long device_address) throws IOException; + + public final synchronized void release() throws IOException { + try { + close(); + } finally { + released = true; + nReleaseDevice(device_address, device_interface_address); + } + } + private final static native void nReleaseDevice(long device_address, long device_interface_address); + + public final synchronized void getElementValue(long element_cookie, OSXEvent event) throws IOException { + checkReleased(); + nGetElementValue(device_interface_address, element_cookie, event); + } + private final static native void nGetElementValue(long device_interface_address, long element_cookie, OSXEvent event) throws IOException; + + public final synchronized OSXHIDQueue createQueue(int queue_depth) throws IOException { + checkReleased(); + long queue_address = nCreateQueue(device_interface_address); + return new OSXHIDQueue(queue_address, queue_depth); + } + private final static native long nCreateQueue(long device_interface_address) throws IOException; + + private final void open() throws IOException { + nOpen(device_interface_address); + } + private final static native void nOpen(long device_interface_address) throws IOException; + + private final void close() throws IOException { + nClose(device_interface_address); + } + private final static native void nClose(long device_interface_address) throws IOException; + + private final void checkReleased() throws IOException { + if (released) + throw new IOException(); + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXHIDDeviceIterator.java b/src/plugins/osx/net/java/games/input/OSXHIDDeviceIterator.java new file mode 100644 index 0000000..1070c58 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXHIDDeviceIterator.java @@ -0,0 +1,65 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** OSX HIDManager implementation +* @author elias +* @author gregorypierce +* @version 1.0 +*/ +final class OSXHIDDeviceIterator { + private final long iterator_address; + + public OSXHIDDeviceIterator() throws IOException { + this.iterator_address = nCreateIterator(); + } + private final static native long nCreateIterator(); + + public final void close(){ + nReleaseIterator(iterator_address); + } + private final static native void nReleaseIterator(long iterator_address); + + public final OSXHIDDevice next() throws IOException { + return nNext(iterator_address); + } + private final static native OSXHIDDevice nNext(long iterator_address) throws IOException; +} diff --git a/src/plugins/osx/net/java/games/input/OSXHIDElement.java b/src/plugins/osx/net/java/games/input/OSXHIDElement.java new file mode 100644 index 0000000..a8c0907 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXHIDElement.java @@ -0,0 +1,143 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents an OSX HID Element +* @author elias +* @author gregorypierce +* @version 1.0 +*/ +final class OSXHIDElement { + private final OSXHIDDevice device; + private final UsagePair usage_pair; + private final long element_cookie; + private final ElementType element_type; + private final int min; + private final int max; + private final Component.Identifier identifier; + private final boolean is_relative; + + public OSXHIDElement(OSXHIDDevice device, UsagePair usage_pair, long element_cookie, ElementType element_type, int min, int max, boolean is_relative) { + this.device = device; + this.usage_pair = usage_pair; + this.element_cookie = element_cookie; + this.element_type = element_type; + this.min = min; + this.max = max; + this.identifier = computeIdentifier(); + this.is_relative = is_relative; + } + + private final Component.Identifier computeIdentifier() { + if (usage_pair.getUsagePage() == UsagePage.GENERIC_DESKTOP) { + return ((GenericDesktopUsage)usage_pair.getUsage()).getIdentifier(); + } else if (usage_pair.getUsagePage() == UsagePage.BUTTON) { + return ((ButtonUsage)usage_pair.getUsage()).getIdentifier(); + } else if (usage_pair.getUsagePage() == UsagePage.KEYBOARD_OR_KEYPAD) { + return ((KeyboardUsage)usage_pair.getUsage()).getIdentifier(); + } else + return null; + } + + final Component.Identifier getIdentifier() { + return identifier; + } + + final long getCookie() { + return element_cookie; + } + + final ElementType getType() { + return element_type; + } + + final boolean isRelative() { + return is_relative && identifier instanceof Component.Identifier.Axis; + } + + final boolean isAnalog() { + return identifier instanceof Component.Identifier.Axis && identifier != Component.Identifier.Axis.POV; + } + + private UsagePair getUsagePair() { + return usage_pair; + } + + final void getElementValue(OSXEvent event) throws IOException { + device.getElementValue(element_cookie, event); + } + + final float convertValue(float value) { + if (identifier == Component.Identifier.Axis.POV) { + switch ((int)value) { + case 0: + return Component.POV.UP; + case 1: + return Component.POV.UP_RIGHT; + case 2: + return Component.POV.RIGHT; + case 3: + return Component.POV.DOWN_RIGHT; + case 4: + return Component.POV.DOWN; + case 5: + return Component.POV.DOWN_LEFT; + case 6: + return Component.POV.LEFT; + case 7: + return Component.POV.UP_LEFT; + case 8: + return Component.POV.OFF; + default: + return Component.POV.OFF; + } + } else if (identifier instanceof Component.Identifier.Axis && !is_relative) { + if (min == max) + return 0; + else if (value > max) + value = max; + else if (value < min) + value = min; + return 2*(value - min)/(max - min) - 1; + } else + return value; + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXHIDQueue.java b/src/plugins/osx/net/java/games/input/OSXHIDQueue.java new file mode 100644 index 0000000..f97cf6a --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXHIDQueue.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; + +/** +* @author elias +* @version 1.0 +*/ +final class OSXHIDQueue { + private final Map map = new HashMap<>(); + private final long queue_address; + + private boolean released; + + public OSXHIDQueue(long address, int queue_depth) throws IOException { + this.queue_address = address; + try { + createQueue(queue_depth); + } catch (IOException e) { + release(); + throw e; + } + } + + public final synchronized void setQueueDepth(int queue_depth) throws IOException { + checkReleased(); + stop(); + close(); + createQueue(queue_depth); + } + + private final void createQueue(int queue_depth) throws IOException { + open(queue_depth); + try { + start(); + } catch (IOException e) { + close(); + throw e; + } + } + + public final OSXComponent mapEvent(OSXEvent event) { + return map.get(event.getCookie()); + } + + private final void open(int queue_depth) throws IOException { + nOpen(queue_address, queue_depth); + } + private final static native void nOpen(long queue_address, int queue_depth) throws IOException; + + private final void close() throws IOException { + nClose(queue_address); + } + private final static native void nClose(long queue_address) throws IOException; + + private final void start() throws IOException { + nStart(queue_address); + } + private final static native void nStart(long queue_address) throws IOException; + + private final void stop() throws IOException { + nStop(queue_address); + } + private final static native void nStop(long queue_address) throws IOException; + + public final synchronized void release() throws IOException { + if (!released) { + released = true; + try { + stop(); + close(); + } finally { + nReleaseQueue(queue_address); + } + } + } + private final static native void nReleaseQueue(long queue_address) throws IOException; + + public final void addElement(OSXHIDElement element, OSXComponent component) throws IOException { + nAddElement(queue_address, element.getCookie()); + map.put(element.getCookie(), component); + } + private final static native void nAddElement(long queue_address, long cookie) throws IOException; + + public final void removeElement(OSXHIDElement element) throws IOException { + nRemoveElement(queue_address, element.getCookie()); + map.remove(element.getCookie()); + } + private final static native void nRemoveElement(long queue_address, long cookie) throws IOException; + + public final synchronized boolean getNextEvent(OSXEvent event) throws IOException { + checkReleased(); + return nGetNextEvent(queue_address, event); + } + private final static native boolean nGetNextEvent(long queue_address, OSXEvent event) throws IOException; + + private final void checkReleased() throws IOException { + if (released) + throw new IOException("Queue is released"); + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXKeyboard.java b/src/plugins/osx/net/java/games/input/OSXKeyboard.java new file mode 100644 index 0000000..f545264 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXKeyboard.java @@ -0,0 +1,68 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents an OSX Keyboard +* @author elias +* @version 1.0 +*/ +final class OSXKeyboard extends Keyboard { + private final PortType port; + private final OSXHIDQueue queue; + + protected OSXKeyboard(OSXHIDDevice device, OSXHIDQueue queue, Component[] components, Controller[] children, Rumbler[] rumblers) { + super(device.getProductName(), components, children, rumblers); + this.queue = queue; + this.port = device.getPortType(); + } + + protected final boolean getNextDeviceEvent(Event event) throws IOException { + return OSXControllers.getNextDeviceEvent(event, queue); + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + queue.setQueueDepth(size); + } + + public final PortType getPortType() { + return port; + } +} diff --git a/src/plugins/osx/net/java/games/input/OSXMouse.java b/src/plugins/osx/net/java/games/input/OSXMouse.java new file mode 100644 index 0000000..8f5244b --- /dev/null +++ b/src/plugins/osx/net/java/games/input/OSXMouse.java @@ -0,0 +1,68 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Represents an OSX Mouse +* @author elias +* @version 1.0 +*/ +final class OSXMouse extends Mouse { + private final PortType port; + private final OSXHIDQueue queue; + + protected OSXMouse(OSXHIDDevice device, OSXHIDQueue queue, Component[] components, Controller[] children, Rumbler[] rumblers) { + super(device.getProductName(), components, children, rumblers); + this.queue = queue; + this.port = device.getPortType(); + } + + protected final boolean getNextDeviceEvent(Event event) throws IOException { + return OSXControllers.getNextDeviceEvent(event, queue); + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + queue.setQueueDepth(size); + } + + public final PortType getPortType() { + return port; + } +} diff --git a/src/plugins/osx/net/java/games/input/Usage.java b/src/plugins/osx/net/java/games/input/Usage.java new file mode 100644 index 0000000..356ee94 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/Usage.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +/** Generic Desktop Usages +* @author elias +* @version 1.0 +*/ +public interface Usage { +} diff --git a/src/plugins/osx/net/java/games/input/UsagePage.java b/src/plugins/osx/net/java/games/input/UsagePage.java new file mode 100644 index 0000000..59e0b9b --- /dev/null +++ b/src/plugins/osx/net/java/games/input/UsagePage.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +import java.lang.reflect.Method; + +/** HID Usage pages +* @author elias +* @version 1.0 +*/ +final class UsagePage { + private final static UsagePage[] map = new UsagePage[0xFF]; + + public final static UsagePage UNDEFINED = new UsagePage(0x00); + public final static UsagePage GENERIC_DESKTOP = new UsagePage(0x01, GenericDesktopUsage.class); + public final static UsagePage SIMULATION = new UsagePage(0x02); + public final static UsagePage VR = new UsagePage(0x03); + public final static UsagePage SPORT = new UsagePage(0x04); + public final static UsagePage GAME = new UsagePage(0x05); + /* Reserved 0x06 */ + public final static UsagePage KEYBOARD_OR_KEYPAD = new UsagePage(0x07, KeyboardUsage.class); /* USB Device Class Definition for Human Interface Devices (HID). Note: the usage type for all key codes is Selector (Sel). */ + public final static UsagePage LEDS = new UsagePage(0x08); + public final static UsagePage BUTTON = new UsagePage(0x09, ButtonUsage.class); + public final static UsagePage ORDINAL = new UsagePage(0x0A); + public final static UsagePage TELEPHONY = new UsagePage(0x0B); + public final static UsagePage CONSUMER = new UsagePage(0x0C); + public final static UsagePage DIGITIZER = new UsagePage(0x0D); + /* Reserved 0x0E */ + public final static UsagePage PID = new UsagePage(0x0F); /* USB Physical Interface Device definitions for force feedback and related devices. */ + public final static UsagePage UNICODE = new UsagePage(0x10); + /* Reserved 0x11 - 0x13 */ + public final static UsagePage ALPHANUMERIC_DISPLAY = new UsagePage(0x14); + /* Reserved 0x15 - 0x7F */ + /* Monitor 0x80 - 0x83 USB Device Class Definition for Monitor Devices */ + /* Power 0x84 - 0x87 USB Device Class Definition for Power Devices */ + public final static UsagePage POWER_DEVICE = new UsagePage(0x84); /* Power Device Page */ + public final static UsagePage BATTERY_SYSTEM = new UsagePage(0x85); /* Battery System Page */ + /* Reserved 0x88 - 0x8B */ + public final static UsagePage BAR_CODE_SCANNER = new UsagePage(0x8C); /* (Point of Sale) USB Device Class Definition for Bar Code Scanner Devices */ + public final static UsagePage SCALE = new UsagePage(0x8D); /* (Point of Sale) USB Device Class Definition for Scale Devices */ + /* ReservedPointofSalepages 0x8E - 0X8F */ + public final static UsagePage CAMERACONTROL= new UsagePage(0x90); /* USB Device Class Definition for Image Class Devices */ + public final static UsagePage ARCADE = new UsagePage(0x91); /* OAAF Definitions for arcade and coinop related Devices */ + private final Class usage_class; + private final int usage_page_id; + + public final static UsagePage map(int page_id) { + if (page_id < 0 || page_id >= map.length) + return null; + return map[page_id]; + } + + private UsagePage(int page_id, Class usage_class) { + map[page_id] = this; + this.usage_class = usage_class; + this.usage_page_id = page_id; + } + + private UsagePage(int page_id) { + this(page_id, null); + } + + public final String toString() { + return "UsagePage (0x" + Integer.toHexString(usage_page_id) + ")"; + } + + public final Usage mapUsage(int usage_id) { + if (usage_class == null) + return null; + try { + Method map_method = usage_class.getMethod("map", int.class); + Object result = map_method.invoke(null, usage_id); + return (Usage)result; + } catch (Exception e) { + throw new Error(e); + } + } +} diff --git a/src/plugins/osx/net/java/games/input/UsagePair.java b/src/plugins/osx/net/java/games/input/UsagePair.java new file mode 100644 index 0000000..8d0b883 --- /dev/null +++ b/src/plugins/osx/net/java/games/input/UsagePair.java @@ -0,0 +1,76 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +/** Usage/Page pair +* @author elias +* @version 1.0 +*/ +class UsagePair { + private final UsagePage usage_page; + private final Usage usage; + + public UsagePair(UsagePage usage_page, Usage usage) { + this.usage_page = usage_page; + this.usage = usage; + } + + public final UsagePage getUsagePage() { + return usage_page; + } + + public final Usage getUsage() { + return usage; + } + + public final int hashCode() { + return usage.hashCode() ^ usage_page.hashCode(); + } + + public final boolean equals(Object other) { + if (!(other instanceof UsagePair)) + return false; + UsagePair other_pair = (UsagePair)other; + return other_pair.usage.equals(usage) && other_pair.usage_page.equals(usage_page); + } + + public final String toString() { + return "UsagePair: (page = " + usage_page + ", usage = " + usage + ")"; + } +} diff --git a/src/plugins/windows/net/java/games/input/DIAbstractController.java b/src/plugins/windows/net/java/games/input/DIAbstractController.java new file mode 100644 index 0000000..ef75400 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DIAbstractController.java @@ -0,0 +1,72 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** +* @author elias +* @version 1.0 +*/ +final class DIAbstractController extends AbstractController { + private final IDirectInputDevice device; + private final Controller.Type type; + + protected DIAbstractController(IDirectInputDevice device, Component[] components, Controller[] children, Rumbler[] rumblers, Controller.Type type) { + super(device.getProductName(), components, children, rumblers); + this.device = device; + this.type = type; + } + + public final void pollDevice() throws IOException { + device.pollAll(); + } + + protected final boolean getNextDeviceEvent(Event event) throws IOException { + return DIControllers.getNextDeviceEvent(event, device); + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + device.setBufferSize(size); + } + + public final Controller.Type getType() { + return type; + } +} diff --git a/src/plugins/windows/net/java/games/input/DIComponent.java b/src/plugins/windows/net/java/games/input/DIComponent.java new file mode 100644 index 0000000..715c7fb --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DIComponent.java @@ -0,0 +1,74 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** +* @author elias +* @version 1.0 +*/ +final class DIComponent extends AbstractComponent { + private final DIDeviceObject object; + + public DIComponent(Component.Identifier identifier, DIDeviceObject object) { + super(object.getName(), identifier); + this.object = object; + } + + public final boolean isRelative() { + return object.isRelative(); + } + + public final boolean isAnalog() { + return object.isAnalog(); + } + + public final float getDeadZone() { + return object.getDeadzone(); + } + + public final DIDeviceObject getDeviceObject() { + return object; + } + + protected final float poll() throws IOException { + return DIControllers.poll(this, object); + } +} diff --git a/src/plugins/windows/net/java/games/input/DIControllers.java b/src/plugins/windows/net/java/games/input/DIControllers.java new file mode 100644 index 0000000..cb4b2d6 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DIControllers.java @@ -0,0 +1,78 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** +* @author elias +* @version 1.0 +*/ +final class DIControllers { + private final static DIDeviceObjectData di_event = new DIDeviceObjectData(); + + /* synchronized to protect di_event */ + public final static synchronized boolean getNextDeviceEvent(Event event, IDirectInputDevice device) throws IOException { + if (!device.getNextEvent(di_event)) + return false; + DIDeviceObject object = device.mapEvent(di_event); + DIComponent component = device.mapObject(object); + if (component == null) + return false; + int event_value; + if (object.isRelative()) { + event_value = object.getRelativeEventValue(di_event.getData()); + } else { + event_value = di_event.getData(); + } + event.set(component, component.getDeviceObject().convertValue(event_value), di_event.getNanos()); + return true; + } + + public final static float poll(Component component, DIDeviceObject object) throws IOException { + int poll_data = object.getDevice().getPollData(object); + float result; + if (object.isRelative()) { + result = object.getRelativePollValue(poll_data); + } else { + result = poll_data; + } + return object.convertValue(result); + } +} diff --git a/src/plugins/windows/net/java/games/input/DIDeviceObject.java b/src/plugins/windows/net/java/games/input/DIDeviceObject.java new file mode 100644 index 0000000..c54cd7a --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DIDeviceObject.java @@ -0,0 +1,211 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper for DIDEVICEOBJECTINSTANCE + * @author elias + * @version 1.0 + */ +final class DIDeviceObject { + //DirectInput scales wheel deltas by 120 + private final static int WHEEL_SCALE = 120; + + private final IDirectInputDevice device; + private final byte[] guid; + private final int identifier; + private final int type; + private final int instance; + private final int guid_type; + private final int flags; + private final String name; + private final Component.Identifier id; + private final int format_offset; + private final long min; + private final long max; + private final int deadzone; + + /* These are used for emulating relative axes */ + private int last_poll_value; + private int last_event_value; + + public DIDeviceObject(IDirectInputDevice device, Component.Identifier id, byte[] guid, int guid_type, int identifier, int type, int instance, int flags, String name, int format_offset) throws IOException { + this.device = device; + this.id = id; + this.guid = guid; + this.identifier = identifier; + this.type = type; + this.instance = instance; + this.guid_type = guid_type; + this.flags = flags; + this.name = name; + this.format_offset = format_offset; + if (isAxis() && !isRelative()) { + long[] range = device.getRangeProperty(identifier); + this.min = range[0]; + this.max = range[1]; + this.deadzone = device.getDeadzoneProperty(identifier); + } else { + this.min = IDirectInputDevice.DIPROPRANGE_NOMIN; + this.max = IDirectInputDevice.DIPROPRANGE_NOMAX; + this.deadzone = 0; + } + } + + public final synchronized int getRelativePollValue(int current_abs_value) { + if (device.areAxesRelative()) + return current_abs_value; + int rel_value = current_abs_value - last_poll_value; + last_poll_value = current_abs_value; + return rel_value; + } + + public final synchronized int getRelativeEventValue(int current_abs_value) { + if (device.areAxesRelative()) + return current_abs_value; + int rel_value = current_abs_value - last_event_value; + last_event_value = current_abs_value; + return rel_value; + } + + public final int getGUIDType() { + return guid_type; + } + + public final int getFormatOffset() { + return format_offset; + } + + public final IDirectInputDevice getDevice() { + return device; + } + + public final int getDIIdentifier() { + return identifier; + } + + public final Component.Identifier getIdentifier() { + return id; + } + + public final String getName() { + return name; + } + + public final int getInstance() { + return instance; + } + + public final int getType() { + return type; + } + + public final byte[] getGUID() { + return guid; + } + + public final int getFlags() { + return flags; + } + + public final long getMin() { + return min; + } + + public final long getMax() { + return max; + } + + public final float getDeadzone() { + return deadzone; + } + + public final boolean isButton() { + return (type & IDirectInputDevice.DIDFT_BUTTON) != 0; + } + + public final boolean isAxis() { + return (type & IDirectInputDevice.DIDFT_AXIS) != 0; + } + + public final boolean isRelative() { + return isAxis() && (type & IDirectInputDevice.DIDFT_RELAXIS) != 0; + } + + public final boolean isAnalog() { + return isAxis() && id != Component.Identifier.Axis.POV; + } + + public final float convertValue(float value) { + if (getDevice().getType() == IDirectInputDevice.DI8DEVTYPE_MOUSE && id == Component.Identifier.Axis.Z) { + return value/WHEEL_SCALE; + } else if (isButton()) { + return (((int)value) & 0x80) != 0 ? 1 : 0; + } else if (id == Component.Identifier.Axis.POV) { + int int_value = (int)value; + if ((int_value & 0xFFFF) == 0xFFFF) + return Component.POV.OFF; + // DirectInput returns POV directions in hundredths of degree clockwise from north + int slice = 360*100/16; + if (int_value >= 0 && int_value < slice) + return Component.POV.UP; + else if (int_value < 3*slice) + return Component.POV.UP_RIGHT; + else if (int_value < 5*slice) + return Component.POV.RIGHT; + else if (int_value < 7*slice) + return Component.POV.DOWN_RIGHT; + else if (int_value < 9*slice) + return Component.POV.DOWN; + else if (int_value < 11*slice) + return Component.POV.DOWN_LEFT; + else if (int_value < 13*slice) + return Component.POV.LEFT; + else if (int_value < 15*slice) + return Component.POV.UP_LEFT; + else + return Component.POV.UP; + } else if (isAxis() && !isRelative()) { + return 2*(value - min)/(float)(max - min) - 1; + } else + return value; + } + +} diff --git a/src/plugins/windows/net/java/games/input/DIDeviceObjectData.java b/src/plugins/windows/net/java/games/input/DIDeviceObjectData.java new file mode 100644 index 0000000..fa84a9d --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DIDeviceObjectData.java @@ -0,0 +1,73 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** Java wrapper for DIDEVICEOBJECTDATA + * @author elias + * @version 1.0 + */ +final class DIDeviceObjectData { + private int format_offset; + private int data; + private int millis; + private int sequence; + + public final void set(int format_offset, int data, int millis, int sequence) { + this.format_offset = format_offset; + this.data = data; + this.millis = millis; + this.sequence = sequence; + } + + public final void set(DIDeviceObjectData other) { + set(other.format_offset, other.data, other.millis, other.sequence); + } + + public final int getData() { + return data; + } + + public final int getFormatOffset() { + return format_offset; + } + + public final long getNanos() { + return millis*1000000L; + } +} diff --git a/src/plugins/windows/net/java/games/input/DIEffectInfo.java b/src/plugins/windows/net/java/games/input/DIEffectInfo.java new file mode 100644 index 0000000..5ece8a3 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DIEffectInfo.java @@ -0,0 +1,83 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** Java wrapper for DIEFFECTINFO + * @author elias + * @version 1.0 + */ +final class DIEffectInfo { + private final IDirectInputDevice device; + private final byte[] guid; + private final int guid_id; + private final int effect_type; + private final int static_params; + private final int dynamic_params; + private final String name; + + public DIEffectInfo(IDirectInputDevice device, byte[] guid, int guid_id, int effect_type, int static_params, int dynamic_params, String name) { + this.device = device; + this.guid = guid; + this.guid_id = guid_id; + this.effect_type = effect_type; + this.static_params = static_params; + this.dynamic_params = dynamic_params; + this.name = name; + } + + public final byte[] getGUID() { + return guid; + } + + public final int getGUIDId() { + return guid_id; + } + + public final int getDynamicParams() { + return dynamic_params; + } + + public final int getEffectType() { + return effect_type; + } + + public final String getName() { + return name; + } +} diff --git a/src/plugins/windows/net/java/games/input/DIIdentifierMap.java b/src/plugins/windows/net/java/games/input/DIIdentifierMap.java new file mode 100644 index 0000000..106d658 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DIIdentifierMap.java @@ -0,0 +1,546 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** + * @author elias + * @version 1.0 + */ +final class DIIdentifierMap { + public final static int DIK_ESCAPE = 0x01; + public final static int DIK_1 = 0x02; + public final static int DIK_2 = 0x03; + public final static int DIK_3 = 0x04; + public final static int DIK_4 = 0x05; + public final static int DIK_5 = 0x06; + public final static int DIK_6 = 0x07; + public final static int DIK_7 = 0x08; + public final static int DIK_8 = 0x09; + public final static int DIK_9 = 0x0A; + public final static int DIK_0 = 0x0B; + public final static int DIK_MINUS = 0x0C; /* - on main keyboard */ + public final static int DIK_EQUALS = 0x0D; + public final static int DIK_BACK = 0x0E; /* backspace */ + public final static int DIK_TAB = 0x0F; + public final static int DIK_Q = 0x10; + public final static int DIK_W = 0x11; + public final static int DIK_E = 0x12; + public final static int DIK_R = 0x13; + public final static int DIK_T = 0x14; + public final static int DIK_Y = 0x15; + public final static int DIK_U = 0x16; + public final static int DIK_I = 0x17; + public final static int DIK_O = 0x18; + public final static int DIK_P = 0x19; + public final static int DIK_LBRACKET = 0x1A; + public final static int DIK_RBRACKET = 0x1B; + public final static int DIK_RETURN = 0x1C; /* Enter on main keyboard */ + public final static int DIK_LCONTROL = 0x1D; + public final static int DIK_A = 0x1E; + public final static int DIK_S = 0x1F; + public final static int DIK_D = 0x20; + public final static int DIK_F = 0x21; + public final static int DIK_G = 0x22; + public final static int DIK_H = 0x23; + public final static int DIK_J = 0x24; + public final static int DIK_K = 0x25; + public final static int DIK_L = 0x26; + public final static int DIK_SEMICOLON = 0x27; + public final static int DIK_APOSTROPHE = 0x28; + public final static int DIK_GRAVE = 0x29; /* accent grave */ + public final static int DIK_LSHIFT = 0x2A; + public final static int DIK_BACKSLASH = 0x2B; + public final static int DIK_Z = 0x2C; + public final static int DIK_X = 0x2D; + public final static int DIK_C = 0x2E; + public final static int DIK_V = 0x2F; + public final static int DIK_B = 0x30; + public final static int DIK_N = 0x31; + public final static int DIK_M = 0x32; + public final static int DIK_COMMA = 0x33; + public final static int DIK_PERIOD = 0x34; /* . on main keyboard */ + public final static int DIK_SLASH = 0x35; /* / on main keyboard */ + public final static int DIK_RSHIFT = 0x36; + public final static int DIK_MULTIPLY = 0x37; /* * on numeric keypad */ + public final static int DIK_LMENU = 0x38; /* left Alt */ + public final static int DIK_SPACE = 0x39; + public final static int DIK_CAPITAL = 0x3A; + public final static int DIK_F1 = 0x3B; + public final static int DIK_F2 = 0x3C; + public final static int DIK_F3 = 0x3D; + public final static int DIK_F4 = 0x3E; + public final static int DIK_F5 = 0x3F; + public final static int DIK_F6 = 0x40; + public final static int DIK_F7 = 0x41; + public final static int DIK_F8 = 0x42; + public final static int DIK_F9 = 0x43; + public final static int DIK_F10 = 0x44; + public final static int DIK_NUMLOCK = 0x45; + public final static int DIK_SCROLL = 0x46; /* Scroll Lock */ + public final static int DIK_NUMPAD7 = 0x47; + public final static int DIK_NUMPAD8 = 0x48; + public final static int DIK_NUMPAD9 = 0x49; + public final static int DIK_SUBTRACT = 0x4A; /* - on numeric keypad */ + public final static int DIK_NUMPAD4 = 0x4B; + public final static int DIK_NUMPAD5 = 0x4C; + public final static int DIK_NUMPAD6 = 0x4D; + public final static int DIK_ADD = 0x4E; /* + on numeric keypad */ + public final static int DIK_NUMPAD1 = 0x4F; + public final static int DIK_NUMPAD2 = 0x50; + public final static int DIK_NUMPAD3 = 0x51; + public final static int DIK_NUMPAD0 = 0x52; + public final static int DIK_DECIMAL = 0x53; /* . on numeric keypad */ + public final static int DIK_OEM_102 = 0x56; /* <> or \| on RT 102-key keyboard (Non-U.S.) */ + public final static int DIK_F11 = 0x57; + public final static int DIK_F12 = 0x58; + public final static int DIK_F13 = 0x64; /* (NEC PC98) */ + public final static int DIK_F14 = 0x65; /* (NEC PC98) */ + public final static int DIK_F15 = 0x66; /* (NEC PC98) */ + public final static int DIK_KANA = 0x70; /* (Japanese keyboard) */ + public final static int DIK_ABNT_C1 = 0x73; /* /? on Brazilian keyboard */ + public final static int DIK_CONVERT = 0x79; /* (Japanese keyboard) */ + public final static int DIK_NOCONVERT = 0x7B; /* (Japanese keyboard) */ + public final static int DIK_YEN = 0x7D; /* (Japanese keyboard) */ + public final static int DIK_ABNT_C2 = 0x7E; /* Numpad . on Brazilian keyboard */ + public final static int DIK_NUMPADEQUALS = 0x8D; /* = on numeric keypad (NEC PC98) */ + public final static int DIK_PREVTRACK = 0x90; /* Previous Track (DIK_CIRCUMFLEX on Japanese keyboard) */ + public final static int DIK_AT = 0x91; /* (NEC PC98) */ + public final static int DIK_COLON = 0x92; /* (NEC PC98) */ + public final static int DIK_UNDERLINE = 0x93; /* (NEC PC98) */ + public final static int DIK_KANJI = 0x94; /* (Japanese keyboard) */ + public final static int DIK_STOP = 0x95; /* (NEC PC98) */ + public final static int DIK_AX = 0x96; /* (Japan AX) */ + public final static int DIK_UNLABELED = 0x97; /* (J3100) */ + public final static int DIK_NEXTTRACK = 0x99; /* Next Track */ + public final static int DIK_NUMPADENTER = 0x9C; /* Enter on numeric keypad */ + public final static int DIK_RCONTROL = 0x9D; + public final static int DIK_MUTE = 0xA0; /* Mute */ + public final static int DIK_CALCULATOR = 0xA1; /* Calculator */ + public final static int DIK_PLAYPAUSE = 0xA2; /* Play / Pause */ + public final static int DIK_MEDIASTOP = 0xA4; /* Media Stop */ + public final static int DIK_VOLUMEDOWN = 0xAE; /* Volume - */ + public final static int DIK_VOLUMEUP = 0xB0; /* Volume + */ + public final static int DIK_WEBHOME = 0xB2; /* Web home */ + public final static int DIK_NUMPADCOMMA = 0xB3; /* , on numeric keypad (NEC PC98) */ + public final static int DIK_DIVIDE = 0xB5; /* / on numeric keypad */ + public final static int DIK_SYSRQ = 0xB7; + public final static int DIK_RMENU = 0xB8; /* right Alt */ + public final static int DIK_PAUSE = 0xC5; /* Pause */ + public final static int DIK_HOME = 0xC7; /* Home on arrow keypad */ + public final static int DIK_UP = 0xC8; /* UpArrow on arrow keypad */ + public final static int DIK_PRIOR = 0xC9; /* PgUp on arrow keypad */ + public final static int DIK_LEFT = 0xCB; /* LeftArrow on arrow keypad */ + public final static int DIK_RIGHT = 0xCD; /* RightArrow on arrow keypad */ + public final static int DIK_END = 0xCF; /* End on arrow keypad */ + public final static int DIK_DOWN = 0xD0; /* DownArrow on arrow keypad */ + public final static int DIK_NEXT = 0xD1; /* PgDn on arrow keypad */ + public final static int DIK_INSERT = 0xD2; /* Insert on arrow keypad */ + public final static int DIK_DELETE = 0xD3; /* Delete on arrow keypad */ + public final static int DIK_LWIN = 0xDB; /* Left Windows key */ + public final static int DIK_RWIN = 0xDC; /* Right Windows key */ + public final static int DIK_APPS = 0xDD; /* AppMenu key */ + public final static int DIK_POWER = 0xDE; /* System Power */ + public final static int DIK_SLEEP = 0xDF; /* System Sleep */ + public final static int DIK_WAKE = 0xE3; /* System Wake */ + public final static int DIK_WEBSEARCH = 0xE5; /* Web Search */ + public final static int DIK_WEBFAVORITES = 0xE6; /* Web Favorites */ + public final static int DIK_WEBREFRESH = 0xE7; /* Web Refresh */ + public final static int DIK_WEBSTOP = 0xE8; /* Web Stop */ + public final static int DIK_WEBFORWARD = 0xE9; /* Web Forward */ + public final static int DIK_WEBBACK = 0xEA; /* Web Back */ + public final static int DIK_MYCOMPUTER = 0xEB; /* My Computer */ + public final static int DIK_MAIL = 0xEC; /* Mail */ + public final static int DIK_MEDIASELECT = 0xED; /* Media Select */ + + public final static Component.Identifier.Key getKeyIdentifier(int key_code) { + switch (key_code) { + case DIK_ESCAPE: + return Component.Identifier.Key.ESCAPE; + case DIK_1: + return Component.Identifier.Key._1; + case DIK_2: + return Component.Identifier.Key._2; + case DIK_3: + return Component.Identifier.Key._3; + case DIK_4: + return Component.Identifier.Key._4; + case DIK_5: + return Component.Identifier.Key._5; + case DIK_6: + return Component.Identifier.Key._6; + case DIK_7: + return Component.Identifier.Key._7; + case DIK_8: + return Component.Identifier.Key._8; + case DIK_9: + return Component.Identifier.Key._9; + case DIK_0: + return Component.Identifier.Key._0; + case DIK_MINUS: + return Component.Identifier.Key.MINUS; + case DIK_EQUALS: + return Component.Identifier.Key.EQUALS; + case DIK_BACK: + return Component.Identifier.Key.BACK; + case DIK_TAB: + return Component.Identifier.Key.TAB; + case DIK_Q: + return Component.Identifier.Key.Q; + case DIK_W: + return Component.Identifier.Key.W; + case DIK_E: + return Component.Identifier.Key.E; + case DIK_R: + return Component.Identifier.Key.R; + case DIK_T: + return Component.Identifier.Key.T; + case DIK_Y: + return Component.Identifier.Key.Y; + case DIK_U: + return Component.Identifier.Key.U; + case DIK_I: + return Component.Identifier.Key.I; + case DIK_O: + return Component.Identifier.Key.O; + case DIK_P: + return Component.Identifier.Key.P; + case DIK_LBRACKET: + return Component.Identifier.Key.LBRACKET; + case DIK_RBRACKET: + return Component.Identifier.Key.RBRACKET; + case DIK_RETURN: + return Component.Identifier.Key.RETURN; + case DIK_LCONTROL: + return Component.Identifier.Key.LCONTROL; + case DIK_A: + return Component.Identifier.Key.A; + case DIK_S: + return Component.Identifier.Key.S; + case DIK_D: + return Component.Identifier.Key.D; + case DIK_F: + return Component.Identifier.Key.F; + case DIK_G: + return Component.Identifier.Key.G; + case DIK_H: + return Component.Identifier.Key.H; + case DIK_J: + return Component.Identifier.Key.J; + case DIK_K: + return Component.Identifier.Key.K; + case DIK_L: + return Component.Identifier.Key.L; + case DIK_SEMICOLON: + return Component.Identifier.Key.SEMICOLON; + case DIK_APOSTROPHE: + return Component.Identifier.Key.APOSTROPHE; + case DIK_GRAVE: + return Component.Identifier.Key.GRAVE; + case DIK_LSHIFT: + return Component.Identifier.Key.LSHIFT; + case DIK_BACKSLASH: + return Component.Identifier.Key.BACKSLASH; + case DIK_Z: + return Component.Identifier.Key.Z; + case DIK_X: + return Component.Identifier.Key.X; + case DIK_C: + return Component.Identifier.Key.C; + case DIK_V: + return Component.Identifier.Key.V; + case DIK_B: + return Component.Identifier.Key.B; + case DIK_N: + return Component.Identifier.Key.N; + case DIK_M: + return Component.Identifier.Key.M; + case DIK_COMMA: + return Component.Identifier.Key.COMMA; + case DIK_PERIOD: + return Component.Identifier.Key.PERIOD; + case DIK_SLASH: + return Component.Identifier.Key.SLASH; + case DIK_RSHIFT: + return Component.Identifier.Key.RSHIFT; + case DIK_MULTIPLY: + return Component.Identifier.Key.MULTIPLY; + case DIK_LMENU: + return Component.Identifier.Key.LALT; + case DIK_SPACE: + return Component.Identifier.Key.SPACE; + case DIK_CAPITAL: + return Component.Identifier.Key.CAPITAL; + case DIK_F1: + return Component.Identifier.Key.F1; + case DIK_F2: + return Component.Identifier.Key.F2; + case DIK_F3: + return Component.Identifier.Key.F3; + case DIK_F4: + return Component.Identifier.Key.F4; + case DIK_F5: + return Component.Identifier.Key.F5; + case DIK_F6: + return Component.Identifier.Key.F6; + case DIK_F7: + return Component.Identifier.Key.F7; + case DIK_F8: + return Component.Identifier.Key.F8; + case DIK_F9: + return Component.Identifier.Key.F9; + case DIK_F10: + return Component.Identifier.Key.F10; + case DIK_NUMLOCK: + return Component.Identifier.Key.NUMLOCK; + case DIK_SCROLL: + return Component.Identifier.Key.SCROLL; + case DIK_NUMPAD7: + return Component.Identifier.Key.NUMPAD7; + case DIK_NUMPAD8: + return Component.Identifier.Key.NUMPAD8; + case DIK_NUMPAD9: + return Component.Identifier.Key.NUMPAD9; + case DIK_SUBTRACT: + return Component.Identifier.Key.SUBTRACT; + case DIK_NUMPAD4: + return Component.Identifier.Key.NUMPAD4; + case DIK_NUMPAD5: + return Component.Identifier.Key.NUMPAD5; + case DIK_NUMPAD6: + return Component.Identifier.Key.NUMPAD6; + case DIK_ADD: + return Component.Identifier.Key.ADD; + case DIK_NUMPAD1: + return Component.Identifier.Key.NUMPAD1; + case DIK_NUMPAD2: + return Component.Identifier.Key.NUMPAD2; + case DIK_NUMPAD3: + return Component.Identifier.Key.NUMPAD3; + case DIK_NUMPAD0: + return Component.Identifier.Key.NUMPAD0; + case DIK_DECIMAL: + return Component.Identifier.Key.DECIMAL; + case DIK_F11: + return Component.Identifier.Key.F11; + case DIK_F12: + return Component.Identifier.Key.F12; + case DIK_F13: + return Component.Identifier.Key.F13; + case DIK_F14: + return Component.Identifier.Key.F14; + case DIK_F15: + return Component.Identifier.Key.F15; + case DIK_KANA: + return Component.Identifier.Key.KANA; + case DIK_CONVERT: + return Component.Identifier.Key.CONVERT; + case DIK_NOCONVERT: + return Component.Identifier.Key.NOCONVERT; + case DIK_YEN: + return Component.Identifier.Key.YEN; + case DIK_NUMPADEQUALS: + return Component.Identifier.Key.NUMPADEQUAL; + case DIK_AT: + return Component.Identifier.Key.AT; + case DIK_COLON: + return Component.Identifier.Key.COLON; + case DIK_UNDERLINE: + return Component.Identifier.Key.UNDERLINE; + case DIK_KANJI: + return Component.Identifier.Key.KANJI; + case DIK_STOP: + return Component.Identifier.Key.STOP; + case DIK_AX: + return Component.Identifier.Key.AX; + case DIK_UNLABELED: + return Component.Identifier.Key.UNLABELED; + case DIK_NUMPADENTER: + return Component.Identifier.Key.NUMPADENTER; + case DIK_RCONTROL: + return Component.Identifier.Key.RCONTROL; + case DIK_NUMPADCOMMA: + return Component.Identifier.Key.NUMPADCOMMA; + case DIK_DIVIDE: + return Component.Identifier.Key.DIVIDE; + case DIK_SYSRQ: + return Component.Identifier.Key.SYSRQ; + case DIK_RMENU: + return Component.Identifier.Key.RALT; + case DIK_PAUSE: + return Component.Identifier.Key.PAUSE; + case DIK_HOME: + return Component.Identifier.Key.HOME; + case DIK_UP: + return Component.Identifier.Key.UP; + case DIK_PRIOR: + return Component.Identifier.Key.PAGEUP; + case DIK_LEFT: + return Component.Identifier.Key.LEFT; + case DIK_RIGHT: + return Component.Identifier.Key.RIGHT; + case DIK_END: + return Component.Identifier.Key.END; + case DIK_DOWN: + return Component.Identifier.Key.DOWN; + case DIK_NEXT: + return Component.Identifier.Key.PAGEDOWN; + case DIK_INSERT: + return Component.Identifier.Key.INSERT; + case DIK_DELETE: + return Component.Identifier.Key.DELETE; + case DIK_LWIN: + return Component.Identifier.Key.LWIN; + case DIK_RWIN: + return Component.Identifier.Key.RWIN; + case DIK_APPS: + return Component.Identifier.Key.APPS; + case DIK_POWER: + return Component.Identifier.Key.POWER; + case DIK_SLEEP: + return Component.Identifier.Key.SLEEP; + /* Unassigned keys */ + case DIK_ABNT_C1: + case DIK_ABNT_C2: + case DIK_PREVTRACK: + case DIK_PLAYPAUSE: + case DIK_NEXTTRACK: + case DIK_MUTE: + case DIK_CALCULATOR: + case DIK_MEDIASTOP: + case DIK_VOLUMEDOWN: + case DIK_VOLUMEUP: + case DIK_WEBHOME: + case DIK_WAKE: + case DIK_WEBSEARCH: + case DIK_WEBFAVORITES: + case DIK_WEBREFRESH: + case DIK_WEBSTOP: + case DIK_WEBFORWARD: + case DIK_WEBBACK: + case DIK_MYCOMPUTER: + case DIK_MAIL: + case DIK_MEDIASELECT: + case DIK_OEM_102: + default: + return Component.Identifier.Key.UNKNOWN; + } + } + + public final static Component.Identifier.Button getButtonIdentifier(int id) { + switch (id) { + case 0: + return Component.Identifier.Button._0; + case 1: + return Component.Identifier.Button._1; + case 2: + return Component.Identifier.Button._2; + case 3: + return Component.Identifier.Button._3; + case 4: + return Component.Identifier.Button._4; + case 5: + return Component.Identifier.Button._5; + case 6: + return Component.Identifier.Button._6; + case 7: + return Component.Identifier.Button._7; + case 8: + return Component.Identifier.Button._8; + case 9: + return Component.Identifier.Button._9; + case 10: + return Component.Identifier.Button._10; + case 11: + return Component.Identifier.Button._11; + case 12: + return Component.Identifier.Button._12; + case 13: + return Component.Identifier.Button._13; + case 14: + return Component.Identifier.Button._14; + case 15: + return Component.Identifier.Button._15; + case 16: + return Component.Identifier.Button._16; + case 17: + return Component.Identifier.Button._17; + case 18: + return Component.Identifier.Button._18; + case 19: + return Component.Identifier.Button._19; + case 20: + return Component.Identifier.Button._20; + case 21: + return Component.Identifier.Button._21; + case 22: + return Component.Identifier.Button._22; + case 23: + return Component.Identifier.Button._23; + case 24: + return Component.Identifier.Button._24; + case 25: + return Component.Identifier.Button._25; + case 26: + return Component.Identifier.Button._26; + case 27: + return Component.Identifier.Button._27; + case 28: + return Component.Identifier.Button._28; + case 29: + return Component.Identifier.Button._29; + case 30: + return Component.Identifier.Button._30; + case 31: + return Component.Identifier.Button._31; + default: + return null; + } + } + + public final static Component.Identifier.Button mapMouseButtonIdentifier(Component.Identifier.Button button_id) { + if (button_id == Component.Identifier.Button._0) { + return Component.Identifier.Button.LEFT; + } else if (button_id == Component.Identifier.Button._1) { + return Component.Identifier.Button.RIGHT; + } else if (button_id == Component.Identifier.Button._2) { + return Component.Identifier.Button.MIDDLE; + } else + return button_id; + } +} diff --git a/src/plugins/windows/net/java/games/input/DIKeyboard.java b/src/plugins/windows/net/java/games/input/DIKeyboard.java new file mode 100644 index 0000000..6eec1b7 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DIKeyboard.java @@ -0,0 +1,66 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** +* @author elias +* @version 1.0 +*/ +final class DIKeyboard extends Keyboard { + private final IDirectInputDevice device; + + protected DIKeyboard(IDirectInputDevice device, Component[] components, Controller[] children, Rumbler[] rumblers) { + super(device.getProductName(), components, children, rumblers); + this.device = device; + } + + protected final boolean getNextDeviceEvent(Event event) throws IOException { + return DIControllers.getNextDeviceEvent(event, device); + } + + public final void pollDevice() throws IOException { + device.pollAll(); + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + device.setBufferSize(size); + } +} diff --git a/src/plugins/windows/net/java/games/input/DIMouse.java b/src/plugins/windows/net/java/games/input/DIMouse.java new file mode 100644 index 0000000..e24ff77 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DIMouse.java @@ -0,0 +1,67 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** +* @author elias +* @version 1.0 +*/ +final class DIMouse extends Mouse { + private final IDirectInputDevice device; + + protected DIMouse(IDirectInputDevice device, Component[] components, Controller[] children, Rumbler[] rumblers) { + super(device.getProductName(), components, children, rumblers); + this.device = device; + } + + public final void pollDevice() throws IOException { + device.pollAll(); + } + + protected final boolean getNextDeviceEvent(Event event) throws IOException { + return DIControllers.getNextDeviceEvent(event, device); + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + device.setBufferSize(size); + } + +} diff --git a/src/plugins/windows/net/java/games/input/DataQueue.java b/src/plugins/windows/net/java/games/input/DataQueue.java new file mode 100644 index 0000000..a530bc6 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DataQueue.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; + +/** + * @author elias + * @version 1.0 + */ +final class DataQueue { + private final T[] elements; + private int position; + private int limit; + + @SuppressWarnings("unchecked") + public DataQueue(int size, Class element_type) { + this.elements= (T[])Array.newInstance(element_type, size); + for (int i = 0; i < elements.length; i++) { + try { + elements[i] = element_type.getDeclaredConstructor().newInstance(); + } catch (InstantiationException|IllegalAccessException|NoSuchMethodException|InvocationTargetException e) { + throw new RuntimeException(e); + } + } + clear(); + } + + public final void clear() { + position = 0; + limit = elements.length; + } + + public final int position() { + return position; + } + + public final int limit() { + return limit; + } + + public final T get(int index) { + assert index < limit; + return elements[index]; + } + + public final T get() { + if (!hasRemaining()) + return null; + return get(position++); + } + + public final void compact() { + int index = 0; + while (hasRemaining()) { + swap(position, index); + position++; + index++; + } + position = index; + limit = elements.length; + } + + private final void swap(int index1, int index2) { + T temp = elements[index1]; + elements[index1] = elements[index2]; + elements[index2] = temp; + } + + public final void flip() { + limit = position; + position = 0; + } + + public final boolean hasRemaining() { + return remaining() > 0; + } + + public final int remaining() { + return limit - position; + } + + public final void position(int position) { + this.position = position; + } + + public final T[] getElements() { + return elements; + } +} diff --git a/src/plugins/windows/net/java/games/input/DirectAndRawInputEnvironmentPlugin.java b/src/plugins/windows/net/java/games/input/DirectAndRawInputEnvironmentPlugin.java new file mode 100644 index 0000000..29b6c07 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DirectAndRawInputEnvironmentPlugin.java @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2007 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.util.ArrayList; +import java.util.List; + +/** + * Combines the list of seperate keyboards and mice found with the raw plugin, + * with the game controllers found with direct input. + * + * @author Jeremy + */ +public class DirectAndRawInputEnvironmentPlugin extends ControllerEnvironment { + + private RawInputEnvironmentPlugin rawPlugin; + private DirectInputEnvironmentPlugin dinputPlugin; + private Controller[] controllers = null; + + public DirectAndRawInputEnvironmentPlugin() { + // These two *must* be loaded in this order for raw devices to work. + dinputPlugin = new DirectInputEnvironmentPlugin(); + rawPlugin = new RawInputEnvironmentPlugin(); + } + + /** + * @see net.java.games.input.ControllerEnvironment#getControllers() + */ + public Controller[] getControllers() { + if(controllers == null) { + boolean rawKeyboardFound = false; + boolean rawMouseFound = false; + List tempControllers = new ArrayList<>(); + Controller[] dinputControllers = dinputPlugin.getControllers(); + Controller[] rawControllers = rawPlugin.getControllers(); + for(int i=0;i) () -> { + try { + String lib_path = System.getProperty("net.java.games.input.librarypath"); + if (lib_path != null) + System.load(lib_path + File.separator + System.mapLibraryName(lib_name)); + else + System.loadLibrary(lib_name); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + supported = false; + } + return null; + }); + } + + static String getPrivilegedProperty(final String property) { + return AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(property)); + } + + + static String getPrivilegedProperty(final String property, final String default_value) { + return AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(property, default_value)); + } + + static { + String osName = getPrivilegedProperty("os.name", "").trim(); + if(osName.startsWith("Windows")) { + supported = true; + if("x86".equals(getPrivilegedProperty("os.arch"))) { + loadLibrary("jinput-dx8"); + } else { + loadLibrary("jinput-dx8_64"); + } + } + } + + private final Controller[] controllers; + private final List active_devices = new ArrayList<>(); + private final DummyWindow window; + + /** Creates new DirectInputEnvironment */ + public DirectInputEnvironmentPlugin() { + DummyWindow window = null; + Controller[] controllers = new Controller[]{}; + if(isSupported()) { + try { + window = new DummyWindow(); + try { + controllers = enumControllers(window); + } catch (IOException e) { + window.destroy(); + throw e; + } + } catch (IOException e) { + log("Failed to enumerate devices: " + e.getMessage()); + } + this.window = window; + this.controllers = controllers; + AccessController.doPrivileged((PrivilegedAction) () -> { + Runtime.getRuntime().addShutdownHook(new ShutdownHook()); + return null; + }); + } else { + // These are final fields, so can't set them, then over ride + // them if we are supported. + this.window = null; + this.controllers = controllers; + } + } + + public final Controller[] getControllers() { + return controllers; + } + + private final Component[] createComponents(IDirectInputDevice device, boolean map_mouse_buttons) { + List device_objects = device.getObjects(); + List controller_components = new ArrayList<>(); + for (int i = 0; i < device_objects.size(); i++) { + DIDeviceObject device_object = device_objects.get(i); + Component.Identifier identifier = device_object.getIdentifier(); + if (identifier == null) + continue; + if (map_mouse_buttons && identifier instanceof Component.Identifier.Button) { + identifier = DIIdentifierMap.mapMouseButtonIdentifier((Component.Identifier.Button)identifier); + } + DIComponent component = new DIComponent(identifier, device_object); + controller_components.add(component); + device.registerComponent(device_object, component); + } + Component[] components = new Component[controller_components.size()]; + controller_components.toArray(components); + return components; + } + + private final Mouse createMouseFromDevice(IDirectInputDevice device) { + Component[] components = createComponents(device, true); + Mouse mouse = new DIMouse(device, components, new Controller[]{}, device.getRumblers()); + if (mouse.getX() != null && mouse.getY() != null && mouse.getPrimaryButton() != null) + return mouse; + else + return null; + } + + private final AbstractController createControllerFromDevice(IDirectInputDevice device, Controller.Type type) { + Component[] components = createComponents(device, false); + AbstractController controller = new DIAbstractController(device, components, new Controller[]{}, device.getRumblers(), type); + return controller; + } + + private final Keyboard createKeyboardFromDevice(IDirectInputDevice device) { + Component[] components = createComponents(device, false); + return new DIKeyboard(device, components, new Controller[]{}, device.getRumblers()); + } + + private final Controller createControllerFromDevice(IDirectInputDevice device) { + switch (device.getType()) { + case IDirectInputDevice.DI8DEVTYPE_MOUSE: + return createMouseFromDevice(device); + case IDirectInputDevice.DI8DEVTYPE_KEYBOARD: + return createKeyboardFromDevice(device); + case IDirectInputDevice.DI8DEVTYPE_GAMEPAD: + return createControllerFromDevice(device, Controller.Type.GAMEPAD); + case IDirectInputDevice.DI8DEVTYPE_DRIVING: + return createControllerFromDevice(device, Controller.Type.WHEEL); + case IDirectInputDevice.DI8DEVTYPE_1STPERSON: + /* Fall through */ + case IDirectInputDevice.DI8DEVTYPE_FLIGHT: + /* Fall through */ + case IDirectInputDevice.DI8DEVTYPE_JOYSTICK: + return createControllerFromDevice(device, Controller.Type.STICK); + default: + return createControllerFromDevice(device, Controller.Type.UNKNOWN); + } + } + + private final Controller[] enumControllers(DummyWindow window) throws IOException { + List controllers = new ArrayList<>(); + IDirectInput dinput = new IDirectInput(window); + try { + List devices = dinput.getDevices(); + for (int i = 0; i < devices.size(); i++) { + IDirectInputDevice device = devices.get(i); + Controller controller = createControllerFromDevice(device); + if (controller != null) { + controllers.add(controller); + active_devices.add(device); + } else + device.release(); + } + } finally { + dinput.release(); + } + Controller[] controllers_array = new Controller[controllers.size()]; + controllers.toArray(controllers_array); + return controllers_array; + } + + private final class ShutdownHook extends Thread { + public final void run() { + /* Release the devices to kill off active force feedback effects */ + for (int i = 0; i < active_devices.size(); i++) { + IDirectInputDevice device = active_devices.get(i); + device.release(); + } + /* We won't release the window since it is + * owned by the thread that created the environment. + */ + } + } + + public boolean isSupported() { + return supported; + } +} \ No newline at end of file diff --git a/src/plugins/windows/net/java/games/input/DummyWindow.java b/src/plugins/windows/net/java/games/input/DummyWindow.java new file mode 100644 index 0000000..5c66d9a --- /dev/null +++ b/src/plugins/windows/net/java/games/input/DummyWindow.java @@ -0,0 +1,64 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper for a (dummy) window + * @author martak + * @author elias + * @version 1.0 + */ +final class DummyWindow { + private final long hwnd_address; + + public DummyWindow() throws IOException { + this.hwnd_address = createWindow(); + } + private final static native long createWindow() throws IOException; + + public final void destroy() throws IOException { + nDestroy(hwnd_address); + } + private final static native void nDestroy(long hwnd_address) throws IOException; + + public final long getHwnd() { + return hwnd_address; + } +} diff --git a/src/plugins/windows/net/java/games/input/IDirectInput.java b/src/plugins/windows/net/java/games/input/IDirectInput.java new file mode 100644 index 0000000..e8c929b --- /dev/null +++ b/src/plugins/windows/net/java/games/input/IDirectInput.java @@ -0,0 +1,100 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +/** Java wrapper for IDirectInput + * @author martak + * @author elias + * @version 1.0 + */ +final class IDirectInput { + private final List devices = new ArrayList<>(); + private final long idirectinput_address; + private final DummyWindow window; + + public IDirectInput(DummyWindow window) throws IOException { + this.window = window; + this.idirectinput_address = createIDirectInput(); + try { + enumDevices(); + } catch (IOException e) { + releaseDevices(); + release(); + throw e; + } + } + private final static native long createIDirectInput() throws IOException; + + public final List getDevices() { + return devices; + } + + private final void enumDevices() throws IOException { + nEnumDevices(idirectinput_address); + } + private final native void nEnumDevices(long addr) throws IOException; + + /* This method is called from native code in nEnumDevices + * native side will clean up in case of an exception + */ + private final void addDevice(long address, byte[] instance_guid, byte[] product_guid, int dev_type, int dev_subtype, String instance_name, String product_name) throws IOException { + try { + IDirectInputDevice device = new IDirectInputDevice(window, address, instance_guid, product_guid, dev_type, dev_subtype, instance_name, product_name); + devices.add(device); + } catch (IOException e) { + DirectInputEnvironmentPlugin.log("Failed to initialize device " + product_name + " because of: " + e); + } + } + + public final void releaseDevices() { + for (int i = 0; i < devices.size(); i++) { + IDirectInputDevice device = devices.get(i); + device.release(); + } + } + + public final void release() { + nRelease(idirectinput_address); + } + private final static native void nRelease(long address); +} diff --git a/src/plugins/windows/net/java/games/input/IDirectInputDevice.java b/src/plugins/windows/net/java/games/input/IDirectInputDevice.java new file mode 100644 index 0000000..3fb29f7 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/IDirectInputDevice.java @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; +import java.util.Arrays; + +/** Java wrapper for IDirectInputDevice + * @author martak + * @author elias + * @version 1.0 + */ +final class IDirectInputDevice { + public final static int GUID_XAxis = 1; + public final static int GUID_YAxis = 2; + public final static int GUID_ZAxis = 3; + public final static int GUID_RxAxis = 4; + public final static int GUID_RyAxis = 5; + public final static int GUID_RzAxis = 6; + public final static int GUID_Slider = 7; + public final static int GUID_Button = 8; + public final static int GUID_Key = 9; + public final static int GUID_POV = 10; + public final static int GUID_Unknown = 11; + + public final static int GUID_ConstantForce = 12; + public final static int GUID_RampForce = 13; + public final static int GUID_Square = 14; + public final static int GUID_Sine = 15; + public final static int GUID_Triangle = 16; + public final static int GUID_SawtoothUp = 17; + public final static int GUID_SawtoothDown = 18; + public final static int GUID_Spring = 19; + public final static int GUID_Damper = 20; + public final static int GUID_Inertia = 21; + public final static int GUID_Friction = 22; + public final static int GUID_CustomForce = 23; + + public final static int DI8DEVTYPE_DEVICE = 0x11; + public final static int DI8DEVTYPE_MOUSE = 0x12; + public final static int DI8DEVTYPE_KEYBOARD = 0x13; + public final static int DI8DEVTYPE_JOYSTICK = 0x14; + public final static int DI8DEVTYPE_GAMEPAD = 0x15; + public final static int DI8DEVTYPE_DRIVING = 0x16; + public final static int DI8DEVTYPE_FLIGHT = 0x17; + public final static int DI8DEVTYPE_1STPERSON = 0x18; + public final static int DI8DEVTYPE_DEVICECTRL = 0x19; + public final static int DI8DEVTYPE_SCREENPOINTER = 0x1A; + public final static int DI8DEVTYPE_REMOTE = 0x1B; + public final static int DI8DEVTYPE_SUPPLEMENTAL = 0x1C; + + public final static int DISCL_EXCLUSIVE = 0x00000001; + public final static int DISCL_NONEXCLUSIVE = 0x00000002; + public final static int DISCL_FOREGROUND = 0x00000004; + public final static int DISCL_BACKGROUND = 0x00000008; + public final static int DISCL_NOWINKEY = 0x00000010; + + public final static int DIDFT_ALL = 0x00000000; + + public final static int DIDFT_RELAXIS = 0x00000001; + public final static int DIDFT_ABSAXIS = 0x00000002; + public final static int DIDFT_AXIS = 0x00000003; + + public final static int DIDFT_PSHBUTTON = 0x00000004; + public final static int DIDFT_TGLBUTTON = 0x00000008; + public final static int DIDFT_BUTTON = 0x0000000C; + + public final static int DIDFT_POV = 0x00000010; + public final static int DIDFT_COLLECTION = 0x00000040; + public final static int DIDFT_NODATA = 0x00000080; + + public final static int DIDFT_FFACTUATOR = 0x01000000; + public final static int DIDFT_FFEFFECTTRIGGER = 0x02000000; + public final static int DIDFT_OUTPUT = 0x10000000; + public final static int DIDFT_VENDORDEFINED = 0x04000000; + public final static int DIDFT_ALIAS = 0x08000000; + public final static int DIDFT_OPTIONAL = 0x80000000; + + public final static int DIDFT_NOCOLLECTION = 0x00FFFF00; + + public final static int DIDF_ABSAXIS = 0x00000001; + public final static int DIDF_RELAXIS = 0x00000002; + + public final static int DI_OK = 0x00000000; + public final static int DI_NOEFFECT = 0x00000001; + public final static int DI_PROPNOEFFECT = 0x00000001; + public final static int DI_POLLEDDEVICE = 0x00000002; + + public final static int DI_DOWNLOADSKIPPED = 0x00000003; + public final static int DI_EFFECTRESTARTED = 0x00000004; + public final static int DI_TRUNCATED = 0x00000008; + public final static int DI_SETTINGSNOTSAVED = 0x0000000B; + public final static int DI_TRUNCATEDANDRESTARTED = 0x0000000C; + + public final static int DI_BUFFEROVERFLOW = 0x00000001; + public final static int DIERR_INPUTLOST = 0x8007001E; + public final static int DIERR_NOTACQUIRED = 0x8007001C; + public final static int DIERR_OTHERAPPHASPRIO = 0x80070005; + + public final static int DIDOI_FFACTUATOR = 0x00000001; + public final static int DIDOI_FFEFFECTTRIGGER = 0x00000002; + public final static int DIDOI_POLLED = 0x00008000; + public final static int DIDOI_ASPECTPOSITION = 0x00000100; + public final static int DIDOI_ASPECTVELOCITY = 0x00000200; + public final static int DIDOI_ASPECTACCEL = 0x00000300; + public final static int DIDOI_ASPECTFORCE = 0x00000400; + public final static int DIDOI_ASPECTMASK = 0x00000F00; + public final static int DIDOI_GUIDISUSAGE = 0x00010000; + + public final static int DIEFT_ALL = 0x00000000; + + public final static int DIEFT_CONSTANTFORCE = 0x00000001; + public final static int DIEFT_RAMPFORCE = 0x00000002; + public final static int DIEFT_PERIODIC = 0x00000003; + public final static int DIEFT_CONDITION = 0x00000004; + public final static int DIEFT_CUSTOMFORCE = 0x00000005; + public final static int DIEFT_HARDWARE = 0x000000FF; + public final static int DIEFT_FFATTACK = 0x00000200; + public final static int DIEFT_FFFADE = 0x00000400; + public final static int DIEFT_SATURATION = 0x00000800; + public final static int DIEFT_POSNEGCOEFFICIENTS = 0x00001000; + public final static int DIEFT_POSNEGSATURATION = 0x00002000; + public final static int DIEFT_DEADBAND = 0x00004000; + public final static int DIEFT_STARTDELAY = 0x00008000; + + public final static int DIEFF_OBJECTIDS = 0x00000001; + public final static int DIEFF_OBJECTOFFSETS = 0x00000002; + public final static int DIEFF_CARTESIAN = 0x00000010; + public final static int DIEFF_POLAR = 0x00000020; + public final static int DIEFF_SPHERICAL = 0x00000040; + + public final static int DIEP_DURATION = 0x00000001; + public final static int DIEP_SAMPLEPERIOD = 0x00000002; + public final static int DIEP_GAIN = 0x00000004; + public final static int DIEP_TRIGGERBUTTON = 0x00000008; + public final static int DIEP_TRIGGERREPEATINTERVAL = 0x00000010; + public final static int DIEP_AXES = 0x00000020; + public final static int DIEP_DIRECTION = 0x00000040; + public final static int DIEP_ENVELOPE = 0x00000080; + public final static int DIEP_TYPESPECIFICPARAMS = 0x00000100; + public final static int DIEP_STARTDELAY = 0x00000200; + public final static int DIEP_ALLPARAMS_DX5 = 0x000001FF; + public final static int DIEP_ALLPARAMS = 0x000003FF; + public final static int DIEP_START = 0x20000000; + public final static int DIEP_NORESTART = 0x40000000; + public final static int DIEP_NODOWNLOAD = 0x80000000; + public final static int DIEB_NOTRIGGER = 0xFFFFFFFF; + + public final static int INFINITE = 0xFFFFFFFF; + + public final static int DI_DEGREES = 100; + public final static int DI_FFNOMINALMAX = 10000; + public final static int DI_SECONDS = 1000000; + + public final static int DIPROPRANGE_NOMIN = 0x80000000; + public final static int DIPROPRANGE_NOMAX = 0x7FFFFFFF; + + private final DummyWindow window; + private final long address; + private final int dev_type; + private final int dev_subtype; + private final String instance_name; + private final String product_name; + private final List objects = new ArrayList<>(); + private final List effects = new ArrayList<>(); + private final List rumblers = new ArrayList<>(); + private final int[] device_state; + private final Map object_to_component = new HashMap<>(); + private final boolean axes_in_relative_mode; + + + private boolean released; + private DataQueue queue; + + private int button_counter; + private int current_format_offset; + + public IDirectInputDevice(DummyWindow window, long address, byte[] instance_guid, byte[] product_guid, int dev_type, int dev_subtype, String instance_name, String product_name) throws IOException { + this.window = window; + this.address = address; + this.product_name = product_name; + this.instance_name = instance_name; + this.dev_type = dev_type; + this.dev_subtype = dev_subtype; + // Assume that the caller (native side) releases the device if setup fails + enumObjects(); + try { + enumEffects(); + createRumblers(); + } catch (IOException e) { + DirectInputEnvironmentPlugin.log("Failed to create rumblers: " + e.getMessage()); + } + /* Some DirectInput lamer-designer made the device state + * axis mode be per-device not per-axis, so I'll just + * get all axes as absolute and compensate for relative axes. + * + * Unless, of course, all axes are relative like a mouse device, + * in which case setting the DIDF_ABSAXIS flag will result in + * incorrect axis values returned from GetDeviceData for some + * obscure reason. + */ + boolean all_relative = true; + boolean has_axis = false; + for (int i = 0; i < objects.size(); i++) { + DIDeviceObject obj = objects.get(i); + if (obj.isAxis()) { + has_axis = true; + if (!obj.isRelative()) { + all_relative = false; + break; + } + } + } + this.axes_in_relative_mode = all_relative && has_axis; + int axis_mode = all_relative ? DIDF_RELAXIS : DIDF_ABSAXIS; + setDataFormat(axis_mode); + if (rumblers.size() > 0) { + try { + setCooperativeLevel(DISCL_BACKGROUND | DISCL_EXCLUSIVE); + } catch (IOException e) { + setCooperativeLevel(DISCL_BACKGROUND | DISCL_NONEXCLUSIVE); + } + } else + setCooperativeLevel(DISCL_BACKGROUND | DISCL_NONEXCLUSIVE); + setBufferSize(AbstractController.EVENT_QUEUE_DEPTH); + acquire(); + this.device_state = new int[objects.size()]; + } + + public final boolean areAxesRelative() { + return axes_in_relative_mode; + } + + public final Rumbler[] getRumblers() { + return rumblers.toArray(new Rumbler[]{}); + } + + private final List createRumblers() throws IOException { + DIDeviceObject x_axis = lookupObjectByGUID(GUID_XAxis); +// DIDeviceObject y_axis = lookupObjectByGUID(GUID_YAxis); + if(x_axis == null/* || y_axis == null*/) + return rumblers; + DIDeviceObject[] axes = {x_axis/*, y_axis*/}; + long[] directions = {0/*, 0*/}; + for (int i = 0; i < effects.size(); i++) { + DIEffectInfo info = effects.get(i); + if ((info.getEffectType() & 0xff) == DIEFT_PERIODIC && + (info.getDynamicParams() & DIEP_GAIN) != 0) { + rumblers.add(createPeriodicRumbler(axes, directions, info)); + } + } + return rumblers; + } + + private final Rumbler createPeriodicRumbler(DIDeviceObject[] axes, long[] directions, DIEffectInfo info) throws IOException { + int[] axis_ids = new int[axes.length]; + for (int i = 0; i < axis_ids.length; i++) { + axis_ids[i] = axes[i].getDIIdentifier(); + } + long effect_address = nCreatePeriodicEffect(address, info.getGUID(), DIEFF_CARTESIAN | DIEFF_OBJECTIDS, INFINITE, 0, DI_FFNOMINALMAX, DIEB_NOTRIGGER, 0, axis_ids, directions, 0, 0, 0, 0, DI_FFNOMINALMAX, 0, 0, 50000, 0); + return new IDirectInputEffect(effect_address, info); + } + private final static native long nCreatePeriodicEffect(long address, byte[] effect_guid, int flags, int duration, int sample_period, int gain, int trigger_button, int trigger_repeat_interval, int[] axis_ids, long[] directions, int envelope_attack_level, int envelope_attack_time, int envelope_fade_level, int envelope_fade_time, int periodic_magnitude, int periodic_offset, int periodic_phase, int periodic_period, int start_delay) throws IOException; + + private final DIDeviceObject lookupObjectByGUID(int guid_id) { + for (int i = 0; i < objects.size(); i++) { + DIDeviceObject object = objects.get(i); + if (guid_id == object.getGUIDType()) + return object; + } + return null; + } + + public final int getPollData(DIDeviceObject object) { + return device_state[object.getFormatOffset()]; + } + + public final DIDeviceObject mapEvent(DIDeviceObjectData event) { + /* Raw event format offsets (dwOfs member) is in bytes, + * but we're indexing into ints so we have to compensate + * for the int size (4 bytes) + */ + int format_offset = event.getFormatOffset()/4; + return objects.get(format_offset); + } + + public final DIComponent mapObject(DIDeviceObject object) { + return object_to_component.get(object); + } + + public final void registerComponent(DIDeviceObject object, DIComponent component) { + object_to_component.put(object, component); + } + + public final synchronized void pollAll() throws IOException { + checkReleased(); + poll(); + getDeviceState(device_state); + queue.compact(); + getDeviceData(queue); + queue.flip(); + } + + public synchronized final boolean getNextEvent(DIDeviceObjectData data) { + DIDeviceObjectData next_event = queue.get(); + if (next_event == null) + return false; + data.set(next_event); + return true; + } + + private final void poll() throws IOException { + int res = nPoll(address); + if (res != DI_OK && res != DI_NOEFFECT) { + if (res == DIERR_NOTACQUIRED) { + acquire(); + return; + } + throw new IOException("Failed to poll device (" + Integer.toHexString(res) + ")"); + } + } + private final static native int nPoll(long address) throws IOException; + + private final void acquire() throws IOException { + int res = nAcquire(address); + if (res != DI_OK && res != DIERR_OTHERAPPHASPRIO && res != DI_NOEFFECT) + throw new IOException("Failed to acquire device (" + Integer.toHexString(res) + ")"); + } + private final static native int nAcquire(long address); + + private final void unacquire() throws IOException { + int res = nUnacquire(address); + if (res != DI_OK && res != DI_NOEFFECT) + throw new IOException("Failed to unAcquire device (" + Integer.toHexString(res) + ")"); + } + private final static native int nUnacquire(long address); + + private final boolean getDeviceData(DataQueue queue) throws IOException { + int res = nGetDeviceData(address, 0, queue, queue.getElements(), queue.position(), queue.remaining()); + if (res != DI_OK && res != DI_BUFFEROVERFLOW) { + if (res == DIERR_NOTACQUIRED) { + acquire(); + return false; + } + throw new IOException("Failed to get device data (" + Integer.toHexString(res) + ")"); + } + return true; + } + private final static native int nGetDeviceData(long address, int flags, DataQueue queue, Object[] queue_elements, int position, int remaining); + + private final void getDeviceState(int[] device_state) throws IOException { + int res = nGetDeviceState(address, device_state); + if (res != DI_OK) { + if (res == DIERR_NOTACQUIRED) { + Arrays.fill(device_state, 0); + acquire(); + return; + } + throw new IOException("Failed to get device state (" + Integer.toHexString(res) + ")"); + } + } + private final static native int nGetDeviceState(long address, int[] device_state); + + /* Set a custom data format that maps each object's data into an int[] + array with the same index as in the objects List */ + private final void setDataFormat(int flags) throws IOException { + DIDeviceObject[] device_objects = new DIDeviceObject[objects.size()]; + objects.toArray(device_objects); + int res = nSetDataFormat(address, flags, device_objects); + if (res != DI_OK) + throw new IOException("Failed to set data format (" + Integer.toHexString(res) + ")"); + } + private final static native int nSetDataFormat(long address, int flags, DIDeviceObject[] device_objects); + + public final String getProductName() { + return product_name; + } + + public final int getType() { + return dev_type; + } + + public final List getObjects() { + return objects; + } + + private final void enumEffects() throws IOException { + int res = nEnumEffects(address, DIEFT_ALL); + if (res != DI_OK) + throw new IOException("Failed to enumerate effects (" + Integer.toHexString(res) + ")"); + } + private final native int nEnumEffects(long address, int flags); + + /* Called from native side from nEnumEffects */ + private final void addEffect(byte[] guid, int guid_id, int effect_type, int static_params, int dynamic_params, String name) { + effects.add(new DIEffectInfo(this, guid, guid_id, effect_type, static_params, dynamic_params, name)); + } + + private final void enumObjects() throws IOException { + int res = nEnumObjects(address, DIDFT_BUTTON | DIDFT_AXIS | DIDFT_POV); + if (res != DI_OK) + throw new IOException("Failed to enumerate objects (" + Integer.toHexString(res) + ")"); + } + private final native int nEnumObjects(long address, int flags); + + public final synchronized long[] getRangeProperty(int object_identifier) throws IOException { + checkReleased(); + long[] range = new long[2]; + int res = nGetRangeProperty(address, object_identifier, range); + if (res != DI_OK) + throw new IOException("Failed to get object range (" + res + ")"); + return range; + } + private final static native int nGetRangeProperty(long address, int object_id, long[] range); + + public final synchronized int getDeadzoneProperty(int object_identifier) throws IOException { + checkReleased(); + return nGetDeadzoneProperty(address, object_identifier); + } + private final static native int nGetDeadzoneProperty(long address, int object_id) throws IOException; + + /* Called from native side from nEnumObjects */ + private final void addObject(byte[] guid, int guid_type, int identifier, int type, int instance, int flags, String name) throws IOException { + Component.Identifier id = getIdentifier(guid_type, type, instance); + int format_offset = current_format_offset++; + DIDeviceObject obj = new DIDeviceObject(this, id, guid, guid_type, identifier, type, instance, flags, name, format_offset); + objects.add(obj); + } + + private final static Component.Identifier.Key getKeyIdentifier(int key_instance) { + return DIIdentifierMap.getKeyIdentifier(key_instance); + } + + private final Component.Identifier.Button getNextButtonIdentifier() { + int button_id = button_counter++; + return DIIdentifierMap.getButtonIdentifier(button_id); + } + + private final Component.Identifier getIdentifier(int guid_type, int type, int instance) { + switch (guid_type) { + case IDirectInputDevice.GUID_XAxis: + return Component.Identifier.Axis.X; + case IDirectInputDevice.GUID_YAxis: + return Component.Identifier.Axis.Y; + case IDirectInputDevice.GUID_ZAxis: + return Component.Identifier.Axis.Z; + case IDirectInputDevice.GUID_RxAxis: + return Component.Identifier.Axis.RX; + case IDirectInputDevice.GUID_RyAxis: + return Component.Identifier.Axis.RY; + case IDirectInputDevice.GUID_RzAxis: + return Component.Identifier.Axis.RZ; + case IDirectInputDevice.GUID_Slider: + return Component.Identifier.Axis.SLIDER; + case IDirectInputDevice.GUID_POV: + return Component.Identifier.Axis.POV; + case IDirectInputDevice.GUID_Key: + return getKeyIdentifier(instance); + case IDirectInputDevice.GUID_Button: + return getNextButtonIdentifier(); + default: + return Component.Identifier.Axis.UNKNOWN; + } + } + + public final synchronized void setBufferSize(int size) throws IOException { + checkReleased(); + unacquire(); + int res = nSetBufferSize(address, size); + if (res != DI_OK && res != DI_PROPNOEFFECT && res != DI_POLLEDDEVICE) + throw new IOException("Failed to set buffer size (" + Integer.toHexString(res) + ")"); + queue = new DataQueue<>(size, DIDeviceObjectData.class); + queue.position(queue.limit()); + acquire(); + } + private final static native int nSetBufferSize(long address, int size); + + public final synchronized void setCooperativeLevel(int flags) throws IOException { + checkReleased(); + int res = nSetCooperativeLevel(address, window.getHwnd(), flags); + if (res != DI_OK) + throw new IOException("Failed to set cooperative level (" + Integer.toHexString(res) + ")"); + } + private final static native int nSetCooperativeLevel(long address, long hwnd_address, int flags); + + public synchronized final void release() { + if (!released) { + released = true; + for (int i = 0; i < rumblers.size(); i++) { + IDirectInputEffect effect = (IDirectInputEffect)rumblers.get(i); + effect.release(); + } + nRelease(address); + } + } + private final static native void nRelease(long address); + + private final void checkReleased() throws IOException { + if (released) + throw new IOException("Device is released"); + } + + @SuppressWarnings("deprecation") + protected void finalize() { + release(); + } +} diff --git a/src/plugins/windows/net/java/games/input/IDirectInputEffect.java b/src/plugins/windows/net/java/games/input/IDirectInputEffect.java new file mode 100644 index 0000000..77de468 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/IDirectInputEffect.java @@ -0,0 +1,122 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper for IDirectInputEffect + * @author elias + * @version 1.0 + */ +final class IDirectInputEffect implements Rumbler { + private final long address; + private final DIEffectInfo info; + private boolean released; + + public IDirectInputEffect(long address, DIEffectInfo info) { + this.address = address; + this.info = info; + } + + public final synchronized void rumble(float intensity) { + try { + checkReleased(); + if (intensity > 0) { + int int_gain = Math.round(intensity*IDirectInputDevice.DI_FFNOMINALMAX); + setGain(int_gain); + start(1, 0); + } else + stop(); + } catch (IOException e) { + DirectInputEnvironmentPlugin.log("Failed to set rumbler gain: " + e.getMessage()); + } + } + + public final Component.Identifier getAxisIdentifier() { + return null; + } + + public final String getAxisName() { + return null; + } + + public final synchronized void release() { + if (!released) { + released = true; + nRelease(address); + } + } + private final static native void nRelease(long address); + + private final void checkReleased() throws IOException { + if (released) + throw new IOException(); + } + + private final void setGain(int gain) throws IOException { + int res = nSetGain(address, gain); + if (res != IDirectInputDevice.DI_DOWNLOADSKIPPED && + res != IDirectInputDevice.DI_EFFECTRESTARTED && + res != IDirectInputDevice.DI_OK && + res != IDirectInputDevice.DI_TRUNCATED && + res != IDirectInputDevice.DI_TRUNCATEDANDRESTARTED) { + throw new IOException("Failed to set effect gain (0x" + Integer.toHexString(res) + ")"); + } + } + private final static native int nSetGain(long address, int gain); + + private final void start(int iterations, int flags) throws IOException { + int res = nStart(address, iterations, flags); + if (res != IDirectInputDevice.DI_OK) + throw new IOException("Failed to start effect (0x" + Integer.toHexString(res) + ")"); + } + private final static native int nStart(long address, int iterations, int flags); + + private final void stop() throws IOException { + int res = nStop(address); + if (res != IDirectInputDevice.DI_OK) + throw new IOException("Failed to stop effect (0x" + Integer.toHexString(res) + ")"); + } + private final static native int nStop(long address); + + @SuppressWarnings("deprecation") + protected void finalize() { + release(); + } +} diff --git a/src/plugins/windows/net/java/games/input/RawDevice.java b/src/plugins/windows/net/java/games/input/RawDevice.java new file mode 100644 index 0000000..c7b0696 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawDevice.java @@ -0,0 +1,310 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper of RAWDEVICELIST + * @author elias + * @version 1.0 + */ +final class RawDevice { + public final static int RI_MOUSE_LEFT_BUTTON_DOWN = 0x0001; // Left Button changed to down. + public final static int RI_MOUSE_LEFT_BUTTON_UP = 0x0002; // Left Button changed to up. + public final static int RI_MOUSE_RIGHT_BUTTON_DOWN = 0x0004; // Right Button changed to down. + public final static int RI_MOUSE_RIGHT_BUTTON_UP = 0x0008; // Right Button changed to up. + public final static int RI_MOUSE_MIDDLE_BUTTON_DOWN = 0x0010; // Middle Button changed to down. + public final static int RI_MOUSE_MIDDLE_BUTTON_UP = 0x0020; // Middle Button changed to up. + + public final static int RI_MOUSE_BUTTON_1_DOWN = RI_MOUSE_LEFT_BUTTON_DOWN; + public final static int RI_MOUSE_BUTTON_1_UP = RI_MOUSE_LEFT_BUTTON_UP; + public final static int RI_MOUSE_BUTTON_2_DOWN = RI_MOUSE_RIGHT_BUTTON_DOWN; + public final static int RI_MOUSE_BUTTON_2_UP = RI_MOUSE_RIGHT_BUTTON_UP; + public final static int RI_MOUSE_BUTTON_3_DOWN = RI_MOUSE_MIDDLE_BUTTON_DOWN; + public final static int RI_MOUSE_BUTTON_3_UP = RI_MOUSE_MIDDLE_BUTTON_UP; + + public final static int RI_MOUSE_BUTTON_4_DOWN = 0x0040; + public final static int RI_MOUSE_BUTTON_4_UP = 0x0080; + public final static int RI_MOUSE_BUTTON_5_DOWN = 0x0100; + public final static int RI_MOUSE_BUTTON_5_UP = 0x0200; + + /* + * If usButtonFlags has RI_MOUSE_WHEEL, the wheel delta is stored in usButtonData. + * Take it as a signed value. + */ + public final static int RI_MOUSE_WHEEL = 0x0400; + + public final static int MOUSE_MOVE_RELATIVE = 0; + public final static int MOUSE_MOVE_ABSOLUTE = 1; + public final static int MOUSE_VIRTUAL_DESKTOP = 0x02; // the coordinates are mapped to the virtual desktop + public final static int MOUSE_ATTRIBUTES_CHANGED = 0x04; // requery for mouse attributes + + public final static int RIM_TYPEHID = 2; + public final static int RIM_TYPEKEYBOARD = 1; + public final static int RIM_TYPEMOUSE = 0; + + public final static int WM_KEYDOWN = 0x0100; + public final static int WM_KEYUP = 0x0101; + public final static int WM_SYSKEYDOWN = 0x0104; + public final static int WM_SYSKEYUP = 0x0105; + + private final RawInputEventQueue queue; + private final long handle; + private final int type; + + /* Events from the event queue thread end here */ + private DataQueue keyboard_events; + private DataQueue mouse_events; + + /* After processing in poll*(), the events are placed here */ + private DataQueue processed_keyboard_events; + private DataQueue processed_mouse_events; + + /* mouse state */ + private final boolean[] button_states = new boolean[5]; + private int wheel; + private int relative_x; + private int relative_y; + private int last_x; + private int last_y; + + // Last x, y for converting absolute events to relative + private int event_relative_x; + private int event_relative_y; + private int event_last_x; + private int event_last_y; + + /* keyboard state */ + private final boolean[] key_states = new boolean[0xFF]; + + public RawDevice(RawInputEventQueue queue, long handle, int type) { + this.queue = queue; + this.handle = handle; + this.type = type; + setBufferSize(AbstractController.EVENT_QUEUE_DEPTH); + } + + /* Careful, this is called from the event queue thread */ + public final synchronized void addMouseEvent(long millis, int flags, int button_flags, int button_data, long raw_buttons, long last_x, long last_y, long extra_information) { + if (mouse_events.hasRemaining()) { + RawMouseEvent event = mouse_events.get(); + event.set(millis, flags, button_flags, button_data, raw_buttons, last_x, last_y, extra_information); + } + } + + /* Careful, this is called from the event queue thread */ + public final synchronized void addKeyboardEvent(long millis, int make_code, int flags, int vkey, int message, long extra_information) { + if (keyboard_events.hasRemaining()) { + RawKeyboardEvent event = keyboard_events.get(); + event.set(millis, make_code, flags, vkey, message, extra_information); + } + } + + public final synchronized void pollMouse() { + relative_x = relative_y = wheel = 0; + mouse_events.flip(); + while (mouse_events.hasRemaining()) { + RawMouseEvent event = mouse_events.get(); + boolean has_update = processMouseEvent(event); + if (has_update && processed_mouse_events.hasRemaining()) { + RawMouseEvent processed_event = processed_mouse_events.get(); + processed_event.set(event); + } + } + mouse_events.compact(); + } + + public final synchronized void pollKeyboard() { + keyboard_events.flip(); + while (keyboard_events.hasRemaining()) { + RawKeyboardEvent event = keyboard_events.get(); + boolean has_update = processKeyboardEvent(event); + if (has_update && processed_keyboard_events.hasRemaining()) { + RawKeyboardEvent processed_event = processed_keyboard_events.get(); + processed_event.set(event); + } + } + keyboard_events.compact(); + } + + private final boolean updateButtonState(int button_id, int button_flags, int down_flag, int up_flag) { + if (button_id >= button_states.length) + return false; + if ((button_flags & down_flag) != 0) { + button_states[button_id] = true; + return true; + } else if ((button_flags & up_flag) != 0) { + button_states[button_id] = false; + return true; + } else + return false; + } + + private final boolean processKeyboardEvent(RawKeyboardEvent event) { + int message = event.getMessage(); + int vkey = event.getVKey(); + if (vkey >= key_states.length) + return false; + if (message == WM_KEYDOWN || message == WM_SYSKEYDOWN) { + key_states[vkey] = true; + return true; + } else if (message == WM_KEYUP || message == WM_SYSKEYUP) { + key_states[vkey] = false; + return true; + } else + return false; + } + + public final boolean isKeyDown(int vkey) { + return key_states[vkey]; + } + + private final boolean processMouseEvent(RawMouseEvent event) { + boolean has_update = false; + int button_flags = event.getButtonFlags(); + has_update = updateButtonState(0, button_flags, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP) || has_update; + has_update = updateButtonState(1, button_flags, RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP) || has_update; + has_update = updateButtonState(2, button_flags, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP) || has_update; + has_update = updateButtonState(3, button_flags, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP) || has_update; + has_update = updateButtonState(4, button_flags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP) || has_update; + int dx; + int dy; + if ((event.getFlags() & MOUSE_MOVE_ABSOLUTE) != 0) { + dx = event.getLastX() - last_x; + dy = event.getLastY() - last_y; + last_x = event.getLastX(); + last_y = event.getLastY(); + } else { + dx = event.getLastX(); + dy = event.getLastY(); + } + int dwheel = 0; + if ((button_flags & RI_MOUSE_WHEEL) != 0) + dwheel = event.getWheelDelta(); + relative_x += dx; + relative_y += dy; + wheel += dwheel; + has_update = dx != 0 || dy != 0 || dwheel != 0 || has_update; + return has_update; + } + + public final int getWheel() { + return wheel; + } + + public final int getEventRelativeX() { + return event_relative_x; + } + + public final int getEventRelativeY() { + return event_relative_y; + } + + public final int getRelativeX() { + return relative_x; + } + + public final int getRelativeY() { + return relative_y; + } + + public final synchronized boolean getNextKeyboardEvent(RawKeyboardEvent event) { + processed_keyboard_events.flip(); + if (!processed_keyboard_events.hasRemaining()) { + processed_keyboard_events.compact(); + return false; + } + RawKeyboardEvent next_event = processed_keyboard_events.get(); + event.set(next_event); + processed_keyboard_events.compact(); + return true; + } + + public final synchronized boolean getNextMouseEvent(RawMouseEvent event) { + processed_mouse_events.flip(); + if (!processed_mouse_events.hasRemaining()) { + processed_mouse_events.compact(); + return false; + } + RawMouseEvent next_event = processed_mouse_events.get(); + if ((next_event.getFlags() & MOUSE_MOVE_ABSOLUTE) != 0) { + event_relative_x = next_event.getLastX() - event_last_x; + event_relative_y = next_event.getLastY() - event_last_y; + event_last_x = next_event.getLastX(); + event_last_y = next_event.getLastY(); + } else { + event_relative_x = next_event.getLastX(); + event_relative_y = next_event.getLastY(); + } + event.set(next_event); + processed_mouse_events.compact(); + return true; + } + + public final boolean getButtonState(int button_id) { + if (button_id >= button_states.length) + return false; + return button_states[button_id]; + } + + public final void setBufferSize(int size) { + keyboard_events = new DataQueue<>(size, RawKeyboardEvent.class); + mouse_events = new DataQueue<>(size, RawMouseEvent.class); + processed_keyboard_events = new DataQueue<>(size, RawKeyboardEvent.class); + processed_mouse_events = new DataQueue<>(size, RawMouseEvent.class); + } + + public final int getType() { + return type; + } + + public final long getHandle() { + return handle; + } + + public final String getName() throws IOException { + return nGetName(handle); + } + private final static native String nGetName(long handle) throws IOException; + + public final RawDeviceInfo getInfo() throws IOException { + return nGetInfo(this, handle); + } + private final static native RawDeviceInfo nGetInfo(RawDevice device, long handle) throws IOException; +} diff --git a/src/plugins/windows/net/java/games/input/RawDeviceInfo.java b/src/plugins/windows/net/java/games/input/RawDeviceInfo.java new file mode 100644 index 0000000..7e6228f --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawDeviceInfo.java @@ -0,0 +1,67 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper of RID_DEVICE_INFO + * @author elias + * @version 1.0 + */ +abstract class RawDeviceInfo { + public abstract Controller createControllerFromDevice(RawDevice device, SetupAPIDevice setupapi_device) throws IOException; + + public abstract int getUsage(); + + public abstract int getUsagePage(); + + public abstract long getHandle(); + + public final boolean equals(Object other) { + if (!(other instanceof RawDeviceInfo)) + return false; + RawDeviceInfo other_info = (RawDeviceInfo)other; + return other_info.getUsage() == getUsage() && + other_info.getUsagePage() == getUsagePage(); + } + + public final int hashCode() { + return getUsage() ^ getUsagePage(); + } +} diff --git a/src/plugins/windows/net/java/games/input/RawHIDInfo.java b/src/plugins/windows/net/java/games/input/RawHIDInfo.java new file mode 100644 index 0000000..6465bb4 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawHIDInfo.java @@ -0,0 +1,80 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper of RID_DEVICE_INFO_HID + * @author elias + * @version 1.0 + */ +class RawHIDInfo extends RawDeviceInfo { + private final RawDevice device; + + private final int vendor_id; + private final int product_id; + private final int version; + private final int page; + private final int usage; + + public RawHIDInfo(RawDevice device, int vendor_id, int product_id, int version, int page, int usage) { + this.device = device; + this.vendor_id = vendor_id; + this.product_id = product_id; + this.version = version; + this.page = page; + this.usage = usage; + } + + public final int getUsage() { + return usage; + } + + public final int getUsagePage() { + return page; + } + + public final long getHandle() { + return device.getHandle(); + } + + public final Controller createControllerFromDevice(RawDevice device, SetupAPIDevice setupapi_device) throws IOException { + return null; + } +} diff --git a/src/plugins/windows/net/java/games/input/RawIdentifierMap.java b/src/plugins/windows/net/java/games/input/RawIdentifierMap.java new file mode 100644 index 0000000..662a183 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawIdentifierMap.java @@ -0,0 +1,553 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +/** + * @author elias + * @version 1.0 + */ +final class RawIdentifierMap { + public final static int VK_LBUTTON = 0x01; + public final static int VK_RBUTTON = 0x02; + public final static int VK_CANCEL = 0x03; + public final static int VK_MBUTTON = 0x04; /* NOT contiguous with L & RBUTTON */ + + public final static int VK_XBUTTON1 = 0x05; /* NOT contiguous with L & RBUTTON */ + public final static int VK_XBUTTON2 = 0x06; /* NOT contiguous with L & RBUTTON */ + +/* + * 0x07 : unassigned + */ + + public final static int VK_BACK = 0x08; + public final static int VK_TAB = 0x09; + +/* + * 0x0A - 0x0B : reserved + */ + + public final static int VK_CLEAR = 0x0C; + public final static int VK_RETURN = 0x0D; + + public final static int VK_SHIFT = 0x10; + public final static int VK_CONTROL = 0x11; + public final static int VK_MENU = 0x12; + public final static int VK_PAUSE = 0x13; + public final static int VK_CAPITAL = 0x14; + + public final static int VK_KANA = 0x15; + public final static int VK_HANGEUL = 0x15; /* old name - should be here for compatibility */ + public final static int VK_HANGUL = 0x15; + public final static int VK_JUNJA = 0x17; + public final static int VK_FINAL = 0x18; + public final static int VK_HANJA = 0x19; + public final static int VK_KANJI = 0x19; + + public final static int VK_ESCAPE = 0x1B; + + public final static int VK_CONVERT = 0x1C; + public final static int VK_NONCONVERT = 0x1D; + public final static int VK_ACCEPT = 0x1E; + public final static int VK_MODECHANGE = 0x1F; + + public final static int VK_SPACE = 0x20; + public final static int VK_PRIOR = 0x21; + public final static int VK_NEXT = 0x22; + public final static int VK_END = 0x23; + public final static int VK_HOME = 0x24; + public final static int VK_LEFT = 0x25; + public final static int VK_UP = 0x26; + public final static int VK_RIGHT = 0x27; + public final static int VK_DOWN = 0x28; + public final static int VK_SELECT = 0x29; + public final static int VK_PRINT = 0x2A; + public final static int VK_EXECUTE = 0x2B; + public final static int VK_SNAPSHOT = 0x2C; + public final static int VK_INSERT = 0x2D; + public final static int VK_DELETE = 0x2E; + public final static int VK_HELP = 0x2F; +/* + * VK_0 - VK_9 are the same as ASCII '0' - '9' (0x30 - 0x39) + * 0x40 : unassigned + * VK_A - VK_Z are the same as ASCII 'A' - 'Z' (0x41 - 0x5A) + */ + public final static int VK_0 = 0x30; + public final static int VK_1 = 0x31; + public final static int VK_2 = 0x32; + public final static int VK_3 = 0x33; + public final static int VK_4 = 0x34; + public final static int VK_5 = 0x35; + public final static int VK_6 = 0x36; + public final static int VK_7 = 0x37; + public final static int VK_8 = 0x38; + public final static int VK_9 = 0x39; + + public final static int VK_A = 0x41; + public final static int VK_B = 0x42; + public final static int VK_C = 0x43; + public final static int VK_D = 0x44; + public final static int VK_E = 0x45; + public final static int VK_F = 0x46; + public final static int VK_G = 0x47; + public final static int VK_H = 0x48; + public final static int VK_I = 0x49; + public final static int VK_J = 0x4A; + public final static int VK_K = 0x4B; + public final static int VK_L = 0x4C; + public final static int VK_M = 0x4D; + public final static int VK_N = 0x4E; + public final static int VK_O = 0x4F; + public final static int VK_P = 0x50; + public final static int VK_Q = 0x51; + public final static int VK_R = 0x52; + public final static int VK_S = 0x53; + public final static int VK_T = 0x54; + public final static int VK_U = 0x55; + public final static int VK_V = 0x56; + public final static int VK_W = 0x57; + public final static int VK_X = 0x58; + public final static int VK_Y = 0x59; + public final static int VK_Z = 0x5A; + + public final static int VK_LWIN = 0x5B; + public final static int VK_RWIN = 0x5C; + public final static int VK_APPS = 0x5D; +/* + * 0x5E : reserved; + */ + + public final static int VK_SLEEP = 0x5F; + + public final static int VK_NUMPAD0 = 0x60; + public final static int VK_NUMPAD1 = 0x61; + public final static int VK_NUMPAD2 = 0x62; + public final static int VK_NUMPAD3 = 0x63; + public final static int VK_NUMPAD4 = 0x64; + public final static int VK_NUMPAD5 = 0x65; + public final static int VK_NUMPAD6 = 0x66; + public final static int VK_NUMPAD7 = 0x67; + public final static int VK_NUMPAD8 = 0x68; + public final static int VK_NUMPAD9 = 0x69; + public final static int VK_MULTIPLY = 0x6A; + public final static int VK_ADD = 0x6B; + public final static int VK_SEPARATOR = 0x6C; + public final static int VK_SUBTRACT = 0x6D; + public final static int VK_DECIMAL = 0x6E; + public final static int VK_DIVIDE = 0x6F; + public final static int VK_F1 = 0x70; + public final static int VK_F2 = 0x71; + public final static int VK_F3 = 0x72; + public final static int VK_F4 = 0x73; + public final static int VK_F5 = 0x74; + public final static int VK_F6 = 0x75; + public final static int VK_F7 = 0x76; + public final static int VK_F8 = 0x77; + public final static int VK_F9 = 0x78; + public final static int VK_F10 = 0x79; + public final static int VK_F11 = 0x7A; + public final static int VK_F12 = 0x7B; + public final static int VK_F13 = 0x7C; + public final static int VK_F14 = 0x7D; + public final static int VK_F15 = 0x7E; + public final static int VK_F16 = 0x7F; + public final static int VK_F17 = 0x80; + public final static int VK_F18 = 0x81; + public final static int VK_F19 = 0x82; + public final static int VK_F20 = 0x83; + public final static int VK_F21 = 0x84; + public final static int VK_F22 = 0x85; + public final static int VK_F23 = 0x86; + public final static int VK_F24 = 0x87; + +/* + * 0x88 - 0x8F : unassigned; + */ + + public final static int VK_NUMLOCK = 0x90; + public final static int VK_SCROLL = 0x91; + +/* + * NEC PC-9800 kbd definitions + */ + public final static int VK_OEM_NEC_EQUAL = 0x92; // '=' key on numpad +/* + * Fujitsu/OASYS kbd definitions + */ + public final static int VK_OEM_FJ_JISHO = 0x92; // 'Dictionary' key + public final static int VK_OEM_FJ_MASSHOU = 0x93; // 'Unregister word' key + public final static int VK_OEM_FJ_TOUROKU = 0x94; // 'Register word' key + public final static int VK_OEM_FJ_LOYA = 0x95; // 'Left OYAYUBI' key + public final static int VK_OEM_FJ_ROYA = 0x96; // 'Right OYAYUBI' key + +/* + * 0x97 - 0x9F : unassigned + */ + +/* + * VK_L* & VK_R* - left and right Alt, Ctrl and Shift virtual keys. + * Used only as parameters to GetAsyncKeyState() and GetKeyState(). + * No other API or message will distinguish left and right keys in this way. + */ + public final static int VK_LSHIFT = 0xA0; + public final static int VK_RSHIFT = 0xA1; + public final static int VK_LCONTROL = 0xA2; + public final static int VK_RCONTROL = 0xA3; + public final static int VK_LMENU = 0xA4; + public final static int VK_RMENU = 0xA5; + + public final static int VK_BROWSER_BACK = 0xA6; + public final static int VK_BROWSER_FORWARD = 0xA7; + public final static int VK_BROWSER_REFRESH = 0xA8; + public final static int VK_BROWSER_STOP = 0xA9; + public final static int VK_BROWSER_SEARCH = 0xAA; + public final static int VK_BROWSER_FAVORITES = 0xAB; + public final static int VK_BROWSER_HOME = 0xAC; + + public final static int VK_VOLUME_MUTE = 0xAD; + public final static int VK_VOLUME_DOWN = 0xAE; + public final static int VK_VOLUME_UP = 0xAF; + public final static int VK_MEDIA_NEXT_TRACK = 0xB0; + public final static int VK_MEDIA_PREV_TRACK = 0xB1; + public final static int VK_MEDIA_STOP = 0xB2; + public final static int VK_MEDIA_PLAY_PAUSE = 0xB3; + public final static int VK_LAUNCH_MAIL = 0xB4; + public final static int VK_LAUNCH_MEDIA_SELECT = 0xB5; + public final static int VK_LAUNCH_APP1 = 0xB6; + public final static int VK_LAUNCH_APP2 = 0xB7; + +/* + * 0xB8 - 0xB9 : reserved + */ + + public final static int VK_OEM_1 = 0xBA; // ';:' for US + public final static int VK_OEM_PLUS = 0xBB; // '+' any country + public final static int VK_OEM_COMMA = 0xBC; // ',' any country + public final static int VK_OEM_MINUS = 0xBD; // '-' any country + public final static int VK_OEM_PERIOD = 0xBE; // '.' any country + public final static int VK_OEM_2 = 0xBF; // '/?' for US + public final static int VK_OEM_3 = 0xC0; // '`~' for US + +/* + * 0xC1 - 0xD7 : reserved + */ + +/* + * 0xD8 - 0xDA : unassigned + */ + + public final static int VK_OEM_4 = 0xDB; // '[{' for US + public final static int VK_OEM_5 = 0xDC; // '\|' for US + public final static int VK_OEM_6 = 0xDD; // ']}' for US + public final static int VK_OEM_7 = 0xDE; // ''"' for US + public final static int VK_OEM_8 = 0xDF; + +/* + * 0xE0 : reserved + */ + +/* + * Various extended or enhanced keyboards + */ + public final static int VK_OEM_AX = 0xE1; // 'AX' key on Japanese AX kbd + public final static int VK_OEM_102 = 0xE2; // "<>" or "\|" on RT 102-key kbd. + public final static int VK_ICO_HELP = 0xE3; // Help key on ICO + public final static int VK_ICO_00 = 0xE4; // 00 key on ICO + + public final static int VK_PROCESSKEY = 0xE5; + + public final static int VK_ICO_CLEAR = 0xE6; + + + public final static int VK_PACKET = 0xE7; + +/* + * 0xE8 : unassigned + */ + +/* + * Nokia/Ericsson definitions + */ + public final static int VK_OEM_RESET = 0xE9; + public final static int VK_OEM_JUMP = 0xEA; + public final static int VK_OEM_PA1 = 0xEB; + public final static int VK_OEM_PA2 = 0xEC; + public final static int VK_OEM_PA3 = 0xED; + public final static int VK_OEM_WSCTRL = 0xEE; + public final static int VK_OEM_CUSEL = 0xEF; + public final static int VK_OEM_ATTN = 0xF0; + public final static int VK_OEM_FINISH = 0xF1; + public final static int VK_OEM_COPY = 0xF2; + public final static int VK_OEM_AUTO = 0xF3; + public final static int VK_OEM_ENLW = 0xF4; + public final static int VK_OEM_BACKTAB = 0xF5; + + public final static int VK_ATTN = 0xF6; + public final static int VK_CRSEL = 0xF7; + public final static int VK_EXSEL = 0xF8; + public final static int VK_EREOF = 0xF9; + public final static int VK_PLAY = 0xFA; + public final static int VK_ZOOM = 0xFB; + public final static int VK_NONAME = 0xFC; + public final static int VK_PA1 = 0xFD; + public final static int VK_OEM_CLEAR = 0xFE; + + public final static Component.Identifier.Key mapVKey(int vkey) { + switch (vkey) { + case VK_ESCAPE: + return Component.Identifier.Key.ESCAPE; + case VK_1: + return Component.Identifier.Key._1; + case VK_2: + return Component.Identifier.Key._2; + case VK_3: + return Component.Identifier.Key._3; + case VK_4: + return Component.Identifier.Key._4; + case VK_5: + return Component.Identifier.Key._5; + case VK_6: + return Component.Identifier.Key._6; + case VK_7: + return Component.Identifier.Key._7; + case VK_8: + return Component.Identifier.Key._8; + case VK_9: + return Component.Identifier.Key._9; + case VK_0: + return Component.Identifier.Key._0; + case VK_OEM_NEC_EQUAL: + return Component.Identifier.Key.NUMPADEQUAL; + case VK_BACK: + return Component.Identifier.Key.BACK; + case VK_TAB: + return Component.Identifier.Key.TAB; + case VK_Q: + return Component.Identifier.Key.Q; + case VK_W: + return Component.Identifier.Key.W; + case VK_E: + return Component.Identifier.Key.E; + case VK_R: + return Component.Identifier.Key.R; + case VK_T: + return Component.Identifier.Key.T; + case VK_Y: + return Component.Identifier.Key.Y; + case VK_U: + return Component.Identifier.Key.U; + case VK_I: + return Component.Identifier.Key.I; + case VK_O: + return Component.Identifier.Key.O; + case VK_P: + return Component.Identifier.Key.P; + case VK_OEM_4: + return Component.Identifier.Key.LBRACKET; + case VK_OEM_6: + return Component.Identifier.Key.RBRACKET; + case VK_RETURN: + return Component.Identifier.Key.RETURN; + case VK_CONTROL: + case VK_LCONTROL: + return Component.Identifier.Key.LCONTROL; + case VK_A: + return Component.Identifier.Key.A; + case VK_S: + return Component.Identifier.Key.S; + case VK_D: + return Component.Identifier.Key.D; + case VK_F: + return Component.Identifier.Key.F; + case VK_G: + return Component.Identifier.Key.G; + case VK_H: + return Component.Identifier.Key.H; + case VK_J: + return Component.Identifier.Key.J; + case VK_K: + return Component.Identifier.Key.K; + case VK_L: + return Component.Identifier.Key.L; + case VK_OEM_3: + return Component.Identifier.Key.GRAVE; + case VK_SHIFT: + case VK_LSHIFT: + return Component.Identifier.Key.LSHIFT; + case VK_Z: + return Component.Identifier.Key.Z; + case VK_X: + return Component.Identifier.Key.X; + case VK_C: + return Component.Identifier.Key.C; + case VK_V: + return Component.Identifier.Key.V; + case VK_B: + return Component.Identifier.Key.B; + case VK_N: + return Component.Identifier.Key.N; + case VK_M: + return Component.Identifier.Key.M; + case VK_OEM_COMMA: + return Component.Identifier.Key.COMMA; + case VK_OEM_PERIOD: + return Component.Identifier.Key.PERIOD; + case VK_RSHIFT: + return Component.Identifier.Key.RSHIFT; + case VK_MULTIPLY: + return Component.Identifier.Key.MULTIPLY; + case VK_MENU: + case VK_LMENU: + return Component.Identifier.Key.LALT; + case VK_SPACE: + return Component.Identifier.Key.SPACE; + case VK_CAPITAL: + return Component.Identifier.Key.CAPITAL; + case VK_F1: + return Component.Identifier.Key.F1; + case VK_F2: + return Component.Identifier.Key.F2; + case VK_F3: + return Component.Identifier.Key.F3; + case VK_F4: + return Component.Identifier.Key.F4; + case VK_F5: + return Component.Identifier.Key.F5; + case VK_F6: + return Component.Identifier.Key.F6; + case VK_F7: + return Component.Identifier.Key.F7; + case VK_F8: + return Component.Identifier.Key.F8; + case VK_F9: + return Component.Identifier.Key.F9; + case VK_F10: + return Component.Identifier.Key.F10; + case VK_NUMLOCK: + return Component.Identifier.Key.NUMLOCK; + case VK_SCROLL: + return Component.Identifier.Key.SCROLL; + case VK_NUMPAD7: + return Component.Identifier.Key.NUMPAD7; + case VK_NUMPAD8: + return Component.Identifier.Key.NUMPAD8; + case VK_NUMPAD9: + return Component.Identifier.Key.NUMPAD9; + case VK_SUBTRACT: + return Component.Identifier.Key.SUBTRACT; + case VK_NUMPAD4: + return Component.Identifier.Key.NUMPAD4; + case VK_NUMPAD5: + return Component.Identifier.Key.NUMPAD5; + case VK_NUMPAD6: + return Component.Identifier.Key.NUMPAD6; + case VK_ADD: + return Component.Identifier.Key.ADD; + case VK_NUMPAD1: + return Component.Identifier.Key.NUMPAD1; + case VK_NUMPAD2: + return Component.Identifier.Key.NUMPAD2; + case VK_NUMPAD3: + return Component.Identifier.Key.NUMPAD3; + case VK_NUMPAD0: + return Component.Identifier.Key.NUMPAD0; + case VK_DECIMAL: + return Component.Identifier.Key.DECIMAL; + case VK_F11: + return Component.Identifier.Key.F11; + case VK_F12: + return Component.Identifier.Key.F12; + case VK_F13: + return Component.Identifier.Key.F13; + case VK_F14: + return Component.Identifier.Key.F14; + case VK_F15: + return Component.Identifier.Key.F15; + case VK_KANA: + return Component.Identifier.Key.KANA; + case VK_CONVERT: + return Component.Identifier.Key.CONVERT; + case VK_KANJI: + return Component.Identifier.Key.KANJI; + case VK_OEM_AX: + return Component.Identifier.Key.AX; + case VK_RCONTROL: + return Component.Identifier.Key.RCONTROL; + case VK_SEPARATOR: + return Component.Identifier.Key.NUMPADCOMMA; + case VK_DIVIDE: + return Component.Identifier.Key.DIVIDE; + case VK_SNAPSHOT: + return Component.Identifier.Key.SYSRQ; + case VK_RMENU: + return Component.Identifier.Key.RALT; + case VK_PAUSE: + return Component.Identifier.Key.PAUSE; + case VK_HOME: + return Component.Identifier.Key.HOME; + case VK_UP: + return Component.Identifier.Key.UP; + case VK_PRIOR: + return Component.Identifier.Key.PAGEUP; + case VK_LEFT: + return Component.Identifier.Key.LEFT; + case VK_RIGHT: + return Component.Identifier.Key.RIGHT; + case VK_END: + return Component.Identifier.Key.END; + case VK_DOWN: + return Component.Identifier.Key.DOWN; + case VK_NEXT: + return Component.Identifier.Key.PAGEDOWN; + case VK_INSERT: + return Component.Identifier.Key.INSERT; + case VK_DELETE: + return Component.Identifier.Key.DELETE; + case VK_LWIN: + return Component.Identifier.Key.LWIN; + case VK_RWIN: + return Component.Identifier.Key.RWIN; + case VK_APPS: + return Component.Identifier.Key.APPS; + case VK_SLEEP: + return Component.Identifier.Key.SLEEP; + default: + return Component.Identifier.Key.UNKNOWN; + } + } +} diff --git a/src/plugins/windows/net/java/games/input/RawInputEnvironmentPlugin.java b/src/plugins/windows/net/java/games/input/RawInputEnvironmentPlugin.java new file mode 100644 index 0000000..9d409aa --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawInputEnvironmentPlugin.java @@ -0,0 +1,205 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.List; +import java.util.ArrayList; +import java.io.File; +import java.io.IOException; + +import net.java.games.util.plugins.Plugin; + +/** DirectInput implementation of controller environment + * @author martak + * @author elias + * @version 1.0 + */ +public final class RawInputEnvironmentPlugin extends ControllerEnvironment implements Plugin { + + private static boolean supported = false; + + /** + * Static utility method for loading native libraries. + * It will try to load from either the path given by + * the net.java.games.input.librarypath property + * or through System.loadLibrary(). + * + */ + static void loadLibrary(final String lib_name) { + AccessController.doPrivileged((PrivilegedAction) () -> { + try { + String lib_path = System.getProperty("net.java.games.input.librarypath"); + if (lib_path != null) + System.load(lib_path + File.separator + System.mapLibraryName(lib_name)); + else + System.loadLibrary(lib_name); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + supported = false; + } + return null; + }); + } + + static String getPrivilegedProperty(final String property) { + return AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(property)); + } + + + static String getPrivilegedProperty(final String property, final String default_value) { + return AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(property, default_value)); + } + + static { + String osName = getPrivilegedProperty("os.name", "").trim(); + if(osName.startsWith("Windows")) { + supported = true; + if("x86".equals(getPrivilegedProperty("os.arch"))) { + loadLibrary("jinput-raw"); + } else { + loadLibrary("jinput-raw_64"); + } + } + } + + private final Controller[] controllers; + + /** Creates new DirectInputEnvironment */ + public RawInputEnvironmentPlugin() { + RawInputEventQueue queue; + Controller[] controllers = new Controller[]{}; + if(isSupported()) { + try { + queue = new RawInputEventQueue(); + controllers = enumControllers(queue); + } catch (IOException e) { + log("Failed to enumerate devices: " + e.getMessage()); + } + } + this.controllers = controllers; + } + + public final Controller[] getControllers() { + return controllers; + } + + private final static SetupAPIDevice lookupSetupAPIDevice(String device_name, List setupapi_devices) { + /* First, replace # with / in the device name, since that + * seems to be the format in raw input device name + */ + device_name = device_name.replaceAll("#", "\\\\").toUpperCase(); + for (int i = 0; i < setupapi_devices.size(); i++) { + SetupAPIDevice device = setupapi_devices.get(i); + if (device_name.contains(device.getInstanceId().toUpperCase())) + return device; + } + return null; + } + + private final static void createControllersFromDevices(RawInputEventQueue queue, List controllers, List devices, List setupapi_devices) throws IOException { + List active_devices = new ArrayList<>(); + for (int i = 0; i < devices.size(); i++) { + RawDevice device = devices.get(i); + SetupAPIDevice setupapi_device = lookupSetupAPIDevice(device.getName(), setupapi_devices); + if (setupapi_device == null) { + /* Either the device is an RDP or we failed to locate the + * SetupAPI device that matches + */ + continue; + } + RawDeviceInfo info = device.getInfo(); + Controller controller = info.createControllerFromDevice(device, setupapi_device); + if (controller != null) { + controllers.add(controller); + active_devices.add(device); + } + } + queue.start(active_devices); + } + + private final static native void enumerateDevices(RawInputEventQueue queue, List devices) throws IOException; + + private final Controller[] enumControllers(RawInputEventQueue queue) throws IOException { + List controllers = new ArrayList<>(); + List devices = new ArrayList<>(); + enumerateDevices(queue, devices); + List setupapi_devices = enumSetupAPIDevices(); + createControllersFromDevices(queue, controllers, devices, setupapi_devices); + Controller[] controllers_array = new Controller[controllers.size()]; + controllers.toArray(controllers_array); + return controllers_array; + } + + public boolean isSupported() { + return supported; + } + + /* + * The raw input API, while being able to access + * multiple mice and keyboards, is a bit raw (hah) + * since it lacks some important features: + * + * 1. The list of keyboards and the list of mice + * both include useless Terminal Server + * devices (RDP_MOU and RDP_KEY) that we'd + * like to skip. + * 2. The device names returned by GetRawInputDeviceInfo() + * are not for display, but instead synthesized + * from a combination of a device instance id + * and a GUID. + * + * A solution to both problems is the SetupAPI that allows + * us to enumerate all keyboard and mouse devices and fetch their + * descriptive names and at the same time filter out the unwanted + * RDP devices. + */ + private final static List enumSetupAPIDevices() throws IOException { + List devices = new ArrayList<>(); + nEnumSetupAPIDevices(getKeyboardClassGUID(), devices); + nEnumSetupAPIDevices(getMouseClassGUID(), devices); + return devices; + } + private final static native void nEnumSetupAPIDevices(byte[] guid, List devices) throws IOException; + + private final static native byte[] getKeyboardClassGUID(); + private final static native byte[] getMouseClassGUID(); + +} // class DirectInputEnvironment diff --git a/src/plugins/windows/net/java/games/input/RawInputEventQueue.java b/src/plugins/windows/net/java/games/input/RawInputEventQueue.java new file mode 100644 index 0000000..823f5f5 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawInputEventQueue.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.HashSet; + +/** Java wrapper of RAWDEVICELIST + * @author elias + * @version 1.0 + */ +final class RawInputEventQueue { + private final Object monitor = new Object(); + + private List devices; + + public final void start(List devices) throws IOException { + this.devices = devices; + QueueThread queue = new QueueThread(); + synchronized (monitor) { + queue.start(); + // wait for initialization + while (!queue.isInitialized()) { + try { + monitor.wait(); + } catch (InterruptedException e) {} + } + } + if (queue.getException() != null) + throw queue.getException(); + } + + private final RawDevice lookupDevice(long handle) { + for (int i = 0; i < devices.size(); i++) { + RawDevice device = devices.get(i); + if (device.getHandle() == handle) + return device; + } + return null; + } + + /* Event methods called back from native code in nPoll() */ + private final void addMouseEvent(long handle, long millis, int flags, int button_flags, int button_data, long raw_buttons, long last_x, long last_y, long extra_information) { + RawDevice device = lookupDevice(handle); + if (device == null) + return; + device.addMouseEvent(millis, flags, button_flags, button_data, raw_buttons, last_x, last_y, extra_information); + } + + private final void addKeyboardEvent(long handle, long millis, int make_code, int flags, int vkey, int message, long extra_information) { + RawDevice device = lookupDevice(handle); + if (device == null) + return; + device.addKeyboardEvent(millis, make_code, flags, vkey, message, extra_information); + } + + private final void poll(DummyWindow window) throws IOException { + nPoll(window.getHwnd()); + } + private final native void nPoll(long hwnd_handle) throws IOException; + + private final static void registerDevices(DummyWindow window, RawDeviceInfo[] devices) throws IOException { + nRegisterDevices(0, window.getHwnd(), devices); + } + private final static native void nRegisterDevices(int flags, long hwnd_addr, RawDeviceInfo[] devices) throws IOException; + + private final class QueueThread extends Thread { + private boolean initialized; + private DummyWindow window; + private IOException exception; + + public QueueThread() { + setDaemon(true); + } + + public final boolean isInitialized() { + return initialized; + } + + public final IOException getException() { + return exception; + } + + public final void run() { + // We have to create the window in the (private) queue thread + try { + window = new DummyWindow(); + } catch (IOException e) { + exception = e; + } + initialized = true; + synchronized (monitor) { + monitor.notify(); + } + if (exception != null) + return; + Set active_infos = new HashSet<>(); + try { + for (int i = 0; i < devices.size(); i++) { + RawDevice device = devices.get(i); + active_infos.add(device.getInfo()); + } + RawDeviceInfo[] active_infos_array = new RawDeviceInfo[active_infos.size()]; + active_infos.toArray(active_infos_array); + try { + registerDevices(window, active_infos_array); + while (!isInterrupted()) { + poll(window); + } + } finally { + window.destroy(); + } + } catch (IOException e) { + exception = e; + } + } + } +} diff --git a/src/plugins/windows/net/java/games/input/RawKeyboard.java b/src/plugins/windows/net/java/games/input/RawKeyboard.java new file mode 100644 index 0000000..61a05ed --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawKeyboard.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + */ +package net.java.games.input; + +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** +* @author elias +* @version 1.0 +*/ +final class RawKeyboard extends Keyboard { + private final RawKeyboardEvent raw_event = new RawKeyboardEvent(); + private final RawDevice device; + + protected RawKeyboard(String name, RawDevice device, Controller[] children, Rumbler[] rumblers) throws IOException { + super(name, createKeyboardComponents(device), children, rumblers); + this.device = device; + } + + private final static Component[] createKeyboardComponents(RawDevice device) { + List components = new ArrayList<>(); + Field[] vkey_fields = RawIdentifierMap.class.getFields(); + for (int i = 0; i < vkey_fields.length; i++) { + Field vkey_field = vkey_fields[i]; + try { + if (Modifier.isStatic(vkey_field.getModifiers()) && vkey_field.getType() == int.class) { + int vkey_code = vkey_field.getInt(null); + Component.Identifier.Key key_id = RawIdentifierMap.mapVKey(vkey_code); + if (key_id != Component.Identifier.Key.UNKNOWN) + components.add(new Key(device, vkey_code, key_id)); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + return components.toArray(new Component[]{}); + } + + protected final synchronized boolean getNextDeviceEvent(Event event) throws IOException { + while (true) { + if (!device.getNextKeyboardEvent(raw_event)) + return false; + int vkey = raw_event.getVKey(); + Component.Identifier.Key key_id = RawIdentifierMap.mapVKey(vkey); + Component key = getComponent(key_id); + if (key == null) + continue; + int message = raw_event.getMessage(); + if (message == RawDevice.WM_KEYDOWN || message == RawDevice.WM_SYSKEYDOWN) { + event.set(key, 1, raw_event.getNanos()); + return true; + } else if (message == RawDevice.WM_KEYUP || message == RawDevice.WM_SYSKEYUP) { + event.set(key, 0, raw_event.getNanos()); + return true; + } + } + } + + public final void pollDevice() throws IOException { + device.pollKeyboard(); + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + device.setBufferSize(size); + } + + final static class Key extends AbstractComponent { + private final RawDevice device; + private final int vkey_code; + + public Key(RawDevice device, int vkey_code, Component.Identifier.Key key_id) { + super(key_id.getName(), key_id); + this.device = device; + this.vkey_code = vkey_code; + } + + protected final float poll() throws IOException { + return device.isKeyDown(vkey_code) ? 1f : 0f; + } + + public final boolean isAnalog() { + return false; + } + + public final boolean isRelative() { + return false; + } + } +} diff --git a/src/plugins/windows/net/java/games/input/RawKeyboardEvent.java b/src/plugins/windows/net/java/games/input/RawKeyboardEvent.java new file mode 100644 index 0000000..753baa9 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawKeyboardEvent.java @@ -0,0 +1,79 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper of RAWKEYBOARD + * @author elias + * @version 1.0 + */ +final class RawKeyboardEvent { + private long millis; + private int make_code; + private int flags; + private int vkey; + private int message; + private long extra_information; + + public final void set(long millis, int make_code, int flags, int vkey, int message, long extra_information) { + this.millis = millis; + this.make_code = make_code; + this.flags = flags; + this.vkey = vkey; + this.message = message; + this.extra_information = extra_information; + } + + public final void set(RawKeyboardEvent event) { + set(event.millis, event.make_code, event.flags, event.vkey, event.message, event.extra_information); + } + + public final int getVKey() { + return vkey; + } + + public final int getMessage() { + return message; + } + + public final long getNanos() { + return millis*1000000L; + } +} diff --git a/src/plugins/windows/net/java/games/input/RawKeyboardInfo.java b/src/plugins/windows/net/java/games/input/RawKeyboardInfo.java new file mode 100644 index 0000000..3b4add3 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawKeyboardInfo.java @@ -0,0 +1,81 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper of RID_DEVICE_INFO_KEYBOARD + * @author elias + * @version 1.0 + */ +class RawKeyboardInfo extends RawDeviceInfo { + private final RawDevice device; + private final int type; + private final int sub_type; + private final int keyboard_mode; + private final int num_function_keys; + private final int num_indicators; + private final int num_keys_total; + + public RawKeyboardInfo(RawDevice device, int type, int sub_type, int keyboard_mode, int num_function_keys, int num_indicators, int num_keys_total) { + this.device = device; + this.type = type; + this.sub_type = sub_type; + this.keyboard_mode = keyboard_mode; + this.num_function_keys = num_function_keys; + this.num_indicators = num_indicators; + this.num_keys_total = num_keys_total; + } + + public final int getUsage() { + return 6; + } + + public final int getUsagePage() { + return 1; + } + + public final long getHandle() { + return device.getHandle(); + } + + public final Controller createControllerFromDevice(RawDevice device, SetupAPIDevice setupapi_device) throws IOException { + return new RawKeyboard(setupapi_device.getName(), device, new Controller[]{}, new Rumbler[]{}); + } +} diff --git a/src/plugins/windows/net/java/games/input/RawMouse.java b/src/plugins/windows/net/java/games/input/RawMouse.java new file mode 100644 index 0000000..57b6cc0 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawMouse.java @@ -0,0 +1,214 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** +* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistribution of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistribution in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materails provided with the distribution. +* +* Neither the name Sun Microsystems, Inc. or the names of the contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* This software is provided "AS IS," without a warranty of any kind. +* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING +* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR +* NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND +* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS +* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS +* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST +* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, +* INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY +* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, +* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +* +* You acknowledge that this software is not designed or intended for us in +* the design, construction, operation or maintenance of any nuclear facility +* +*****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** +* @author elias +* @version 1.0 +*/ +final class RawMouse extends Mouse { + /* Because one raw event can contain multiple + * changes, we'll make a simple state machine + * to keep track of which change to report next + */ + + // Another event should be read + private final static int EVENT_DONE = 1; + // The X axis should be reported next + private final static int EVENT_X = 2; + // The Y axis should be reported next + private final static int EVENT_Y = 3; + // The Z axis should be reported next + private final static int EVENT_Z = 4; + // Button 0 should be reported next + private final static int EVENT_BUTTON_0 = 5; + // Button 1 should be reported next + private final static int EVENT_BUTTON_1 = 6; + // Button 2 should be reported next + private final static int EVENT_BUTTON_2 = 7; + // Button 3 should be reported next + private final static int EVENT_BUTTON_3 = 8; + // Button 4 should be reported next + private final static int EVENT_BUTTON_4 = 9; + + private final RawDevice device; + + private final RawMouseEvent current_event = new RawMouseEvent(); + private int event_state = EVENT_DONE; + + protected RawMouse(String name, RawDevice device, Component[] components, Controller[] children, Rumbler[] rumblers) throws IOException { + super(name, components, children, rumblers); + this.device = device; + } + + public final void pollDevice() throws IOException { + device.pollMouse(); + } + + private final static boolean makeButtonEvent(RawMouseEvent mouse_event, Event event, Component button_component, int down_flag, int up_flag) { + if ((mouse_event.getButtonFlags() & down_flag) != 0) { + event.set(button_component, 1, mouse_event.getNanos()); + return true; + } else if ((mouse_event.getButtonFlags() & up_flag) != 0) { + event.set(button_component, 0, mouse_event.getNanos()); + return true; + } else + return false; + } + + protected final synchronized boolean getNextDeviceEvent(Event event) throws IOException { + while (true) { + switch (event_state) { + case EVENT_DONE: + if (!device.getNextMouseEvent(current_event)) + return false; + event_state = EVENT_X; + break; + case EVENT_X: + int rel_x = device.getEventRelativeX(); + event_state = EVENT_Y; + if (rel_x != 0) { + event.set(getX(), rel_x, current_event.getNanos()); + return true; + } + break; + case EVENT_Y: + int rel_y = device.getEventRelativeY(); + event_state = EVENT_Z; + if (rel_y != 0) { + event.set(getY(), rel_y, current_event.getNanos()); + return true; + } + break; + case EVENT_Z: + int wheel = current_event.getWheelDelta(); + event_state = EVENT_BUTTON_0; + if (wheel != 0) { + event.set(getWheel(), wheel, current_event.getNanos()); + return true; + } + break; + case EVENT_BUTTON_0: + event_state = EVENT_BUTTON_1; + if (makeButtonEvent(current_event, event, getPrimaryButton(), RawDevice.RI_MOUSE_BUTTON_1_DOWN, RawDevice.RI_MOUSE_BUTTON_1_UP)) + return true; + break; + case EVENT_BUTTON_1: + event_state = EVENT_BUTTON_2; + if (makeButtonEvent(current_event, event, getSecondaryButton(), RawDevice.RI_MOUSE_BUTTON_2_DOWN, RawDevice.RI_MOUSE_BUTTON_2_UP)) + return true; + break; + case EVENT_BUTTON_2: + event_state = EVENT_BUTTON_3; + if (makeButtonEvent(current_event, event, getTertiaryButton(), RawDevice.RI_MOUSE_BUTTON_3_DOWN, RawDevice.RI_MOUSE_BUTTON_3_UP)) + return true; + break; + case EVENT_BUTTON_3: + event_state = EVENT_BUTTON_4; + if (makeButtonEvent(current_event, event, getButton3(), RawDevice.RI_MOUSE_BUTTON_4_DOWN, RawDevice.RI_MOUSE_BUTTON_4_UP)) + return true; + break; + case EVENT_BUTTON_4: + event_state = EVENT_DONE; + if (makeButtonEvent(current_event, event, getButton4(), RawDevice.RI_MOUSE_BUTTON_5_DOWN, RawDevice.RI_MOUSE_BUTTON_5_UP)) + return true; + break; + default: + throw new RuntimeException("Unknown event state: " + event_state); + } + } + } + + protected final void setDeviceEventQueueSize(int size) throws IOException { + device.setBufferSize(size); + } + + final static class Axis extends AbstractComponent { + private final RawDevice device; + + public Axis(RawDevice device, Component.Identifier.Axis axis) { + super(axis.getName(), axis); + this.device = device; + } + + public final boolean isRelative() { + return true; + } + + public final boolean isAnalog() { + return true; + } + + protected final float poll() throws IOException { + if (getIdentifier() == Component.Identifier.Axis.X) { + return device.getRelativeX(); + } else if (getIdentifier() == Component.Identifier.Axis.Y) { + return device.getRelativeY(); + } else if (getIdentifier() == Component.Identifier.Axis.Z) { + return device.getWheel(); + } else + throw new RuntimeException("Unknown raw axis: " + getIdentifier()); + } + } + + final static class Button extends AbstractComponent { + private final RawDevice device; + private final int button_id; + + public Button(RawDevice device, Component.Identifier.Button id, int button_id) { + super(id.getName(), id); + this.device = device; + this.button_id = button_id; + } + + protected final float poll() throws IOException { + return device.getButtonState(button_id) ? 1 : 0; + } + + public final boolean isAnalog() { + return false; + } + + public final boolean isRelative() { + return false; + } + } +} diff --git a/src/plugins/windows/net/java/games/input/RawMouseEvent.java b/src/plugins/windows/net/java/games/input/RawMouseEvent.java new file mode 100644 index 0000000..1cc8fce --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawMouseEvent.java @@ -0,0 +1,108 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper of RAWMOUSE + * @author elias + * @version 1.0 + */ +final class RawMouseEvent { + /* It seems that raw input scales wheel + * the same way as direcinput + */ + private final static int WHEEL_SCALE = 120; + + private long millis; + private int flags; + private int button_flags; + private int button_data; + private long raw_buttons; + private long last_x; + private long last_y; + private long extra_information; + + public final void set(long millis, int flags, int button_flags, int button_data, long raw_buttons, long last_x, long last_y, long extra_information) { + this.millis = millis; + this.flags = flags; + this.button_flags = button_flags; + this.button_data = button_data; + this.raw_buttons = raw_buttons; + this.last_x = last_x; + this.last_y = last_y; + this.extra_information = extra_information; + } + + public final void set(RawMouseEvent event) { + set(event.millis, event.flags, event.button_flags, event.button_data, event.raw_buttons, event.last_x, event.last_y, event.extra_information); + } + + public final int getWheelDelta() { + return button_data/WHEEL_SCALE; + } + + private final int getButtonData() { + return button_data; + } + + public final int getFlags() { + return flags; + } + + public final int getButtonFlags() { + return button_flags; + } + + public final int getLastX() { + return (int)last_x; + } + + public final int getLastY() { + return (int)last_y; + } + + public final long getRawButtons() { + return raw_buttons; + } + + public final long getNanos() { + return millis*1000000L; + } +} diff --git a/src/plugins/windows/net/java/games/input/RawMouseInfo.java b/src/plugins/windows/net/java/games/input/RawMouseInfo.java new file mode 100644 index 0000000..aa89e94 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/RawMouseInfo.java @@ -0,0 +1,88 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper of RID_DEVICE_INFO_MOUSE + * @author elias + * @version 1.0 + */ +class RawMouseInfo extends RawDeviceInfo { + private final RawDevice device; + private final int id; + private final int num_buttons; + private final int sample_rate; + + public RawMouseInfo(RawDevice device, int id, int num_buttons, int sample_rate) { + this.device = device; + this.id = id; + this.num_buttons = num_buttons; + this.sample_rate = sample_rate; + } + + public final int getUsage() { + return 2; + } + + public final int getUsagePage() { + return 1; + } + + public final long getHandle() { + return device.getHandle(); + } + + public final Controller createControllerFromDevice(RawDevice device, SetupAPIDevice setupapi_device) throws IOException { + if (num_buttons == 0) + return null; + // A raw mouse contains the x and y and z axis and the buttons + Component[] components = new Component[3 + num_buttons]; + int index = 0; + components[index++] = new RawMouse.Axis(device, Component.Identifier.Axis.X); + components[index++] = new RawMouse.Axis(device, Component.Identifier.Axis.Y); + components[index++] = new RawMouse.Axis(device, Component.Identifier.Axis.Z); + for (int i = 0; i < num_buttons; i++) { + Component.Identifier.Button id = DIIdentifierMap.mapMouseButtonIdentifier(DIIdentifierMap.getButtonIdentifier(i)); + components[index++] = new RawMouse.Button(device, id, i); + } + Controller mouse = new RawMouse(setupapi_device.getName(), device, components, new Controller[]{}, new Rumbler[]{}); + return mouse; + } +} diff --git a/src/plugins/windows/net/java/games/input/SetupAPIDevice.java b/src/plugins/windows/net/java/games/input/SetupAPIDevice.java new file mode 100644 index 0000000..1c84545 --- /dev/null +++ b/src/plugins/windows/net/java/games/input/SetupAPIDevice.java @@ -0,0 +1,63 @@ +/* + * %W% %E% + * + * Copyright 2002 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +/***************************************************************************** + * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materails provided with the distribution. + * + * Neither the name Sun Microsystems, Inc. or the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR + * NON-INFRINGEMEN, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND + * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS + * A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES. HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed or intended for us in + * the design, construction, operation or maintenance of any nuclear facility + * + *****************************************************************************/ +package net.java.games.input; + +import java.io.IOException; + +/** Java wrapper of a SetupAPI device + * @author elias + * @version 1.0 + */ +final class SetupAPIDevice { + private final String device_instance_id; + private final String device_name; + + public SetupAPIDevice(String device_instance_id, String device_name) { + this.device_instance_id = device_instance_id; + this.device_name = device_name; + } + + public final String getName() { + return device_name; + } + + public final String getInstanceId() { + return device_instance_id; + } +} diff --git a/src/plugins/wintab/net/java/games/input/WinTabButtonComponent.java b/src/plugins/wintab/net/java/games/input/WinTabButtonComponent.java new file mode 100644 index 0000000..2d74a2d --- /dev/null +++ b/src/plugins/wintab/net/java/games/input/WinTabButtonComponent.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2006 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +public class WinTabButtonComponent extends WinTabComponent { + + private int index; + + protected WinTabButtonComponent(WinTabContext context, int parentDevice, String name, Identifier id, int index) { + super(context, parentDevice, name, id); + this.index = index; + } + + public Event processPacket(WinTabPacket packet) { + Event newEvent = null; + + float newValue = ((packet.PK_BUTTONS & (int)Math.pow(2, index))>0) ? 1.0f : 0.0f; + if(newValue!=getPollData()) { + lastKnownValue = newValue; + + //Generate an event + newEvent = new Event(); + newEvent.set(this, newValue, packet.PK_TIME*1000); + return newEvent; + } + + return newEvent; + } +} diff --git a/src/plugins/wintab/net/java/games/input/WinTabComponent.java b/src/plugins/wintab/net/java/games/input/WinTabComponent.java new file mode 100644 index 0000000..a4c2ad9 --- /dev/null +++ b/src/plugins/wintab/net/java/games/input/WinTabComponent.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2006 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class WinTabComponent extends AbstractComponent { + + private int min; + private int max; + protected float lastKnownValue; + private boolean analog; + + protected WinTabComponent(WinTabContext context, int parentDevice, String name, Identifier id, int min, int max) { + super(name, id); + this.min = min; + this.max = max; + analog = true; + } + + protected WinTabComponent(WinTabContext context, int parentDevice, String name, Identifier id) { + super(name, id); + this.min = 0; + this.max = 1; + analog = false; + } + + protected float poll() throws IOException { + return lastKnownValue; + } + + public boolean isAnalog() { + return analog; + } + + public boolean isRelative() { + // All axis are absolute + return false; + } + + public static List createComponents(WinTabContext context, int parentDevice, int axisId, int[] axisRanges) { + List components = new ArrayList<>(); + Identifier id; + switch(axisId) { + case WinTabDevice.XAxis: + id = Identifier.Axis.X; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[0], axisRanges[1])); + break; + case WinTabDevice.YAxis: + id = Identifier.Axis.Y; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[0], axisRanges[1])); + break; + case WinTabDevice.ZAxis: + id = Identifier.Axis.Z; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[0], axisRanges[1])); + break; + case WinTabDevice.NPressureAxis: + id = Identifier.Axis.X_FORCE; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[0], axisRanges[1])); + break; + case WinTabDevice.TPressureAxis: + id = Identifier.Axis.Y_FORCE; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[0], axisRanges[1])); + break; + case WinTabDevice.OrientationAxis: + id = Identifier.Axis.RX; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[0], axisRanges[1])); + id = Identifier.Axis.RY; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[2], axisRanges[3])); + id = Identifier.Axis.RZ; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[4], axisRanges[5])); + break; + case WinTabDevice.RotationAxis: + id = Identifier.Axis.RX; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[0], axisRanges[1])); + id = Identifier.Axis.RY; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[2], axisRanges[3])); + id = Identifier.Axis.RZ; + components.add(new WinTabComponent(context, parentDevice, id.getName(), id, axisRanges[4], axisRanges[5])); + break; + } + + return components; + } + + public static Collection createButtons(WinTabContext context, int deviceIndex, int numberOfButtons) { + List buttons = new ArrayList<>(); + Identifier id; + + for(int i=0;i buttonIdClass = Identifier.Button.class; + Field idField = buttonIdClass.getField("_" + i); + id = (Identifier)idField.get(null); + buttons.add(new WinTabButtonComponent(context, deviceIndex, id.getName(), id, i)); + } catch (SecurityException|NoSuchFieldException|IllegalArgumentException|IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + return buttons; + } + + public Event processPacket(WinTabPacket packet) { + // Set this to be the old value incase we don't change it + float newValue=lastKnownValue; + + if(getIdentifier()==Identifier.Axis.X) { + newValue = normalise(packet.PK_X); + } + if(getIdentifier()==Identifier.Axis.Y) { + newValue = normalise(packet.PK_Y); + } + if(getIdentifier()==Identifier.Axis.Z) { + newValue = normalise(packet.PK_Z); + } + if(getIdentifier()==Identifier.Axis.X_FORCE) { + newValue = normalise(packet.PK_NORMAL_PRESSURE); + } + if(getIdentifier()==Identifier.Axis.Y_FORCE) { + newValue = normalise(packet.PK_TANGENT_PRESSURE); + } + if(getIdentifier()==Identifier.Axis.RX) { + newValue = normalise(packet.PK_ORIENTATION_ALT); + } + if(getIdentifier()==Identifier.Axis.RY) { + newValue = normalise(packet.PK_ORIENTATION_AZ); + } + if(getIdentifier()==Identifier.Axis.RZ) { + newValue = normalise(packet.PK_ORIENTATION_TWIST); + } + if(newValue!=getPollData()) { + lastKnownValue = newValue; + + //Generate an event + Event newEvent = new Event(); + newEvent.set(this, newValue, packet.PK_TIME*1000); + return newEvent; + } + + return null; + } + + private float normalise(float value) { + if(max == min) return value; + float bottom = max - min; + return (value - min)/bottom; + } + + public static Collection createCursors(WinTabContext context, int deviceIndex, String[] cursorNames) { + Identifier id; + List cursors = new ArrayList<>(); + + for(int i=0;i devices = new ArrayList<>(); + + int numSupportedDevices = nGetNumberOfSupportedDevices(); + for(int i=0;i eventList = new ArrayList<>(); + + private WinTabDevice(WinTabContext context, int index, String name, Component[] components) { + super(name, components, new Controller[0], new Rumbler[0]); + this.context = context; + } + + protected boolean getNextDeviceEvent(Event event) throws IOException { + if(eventList.size()>0) { + Event ourEvent = eventList.remove(0); + event.set(ourEvent); + return true; + } else { + return false; + } + } + + protected void pollDevice() throws IOException { + // Get the data off the native queue. + context.processEvents(); + + super.pollDevice(); + } + + public Type getType() { + return Type.TRACKPAD; + } + + public void processPacket(WinTabPacket packet) { + Component[] components = getComponents(); + for(int i=0;i componentsList = new ArrayList<>(); + + int[] axisDetails = nGetAxisDetails(deviceIndex, XAxis); + if(axisDetails.length==0) { + WinTabEnvironmentPlugin.log("ZAxis not supported"); + } else { + WinTabEnvironmentPlugin.log("Xmin: " + axisDetails[0] + ", Xmax: " + axisDetails[1]); + componentsList.addAll(WinTabComponent.createComponents(context, deviceIndex, XAxis, axisDetails)); + } + + axisDetails = nGetAxisDetails(deviceIndex, YAxis); + if(axisDetails.length==0) { + WinTabEnvironmentPlugin.log("YAxis not supported"); + } else { + WinTabEnvironmentPlugin.log("Ymin: " + axisDetails[0] + ", Ymax: " + axisDetails[1]); + componentsList.addAll(WinTabComponent.createComponents(context, deviceIndex, YAxis, axisDetails)); + } + + axisDetails = nGetAxisDetails(deviceIndex, ZAxis); + if(axisDetails.length==0) { + WinTabEnvironmentPlugin.log("ZAxis not supported"); + } else { + WinTabEnvironmentPlugin.log("Zmin: " + axisDetails[0] + ", Zmax: " + axisDetails[1]); + componentsList.addAll(WinTabComponent.createComponents(context, deviceIndex, ZAxis, axisDetails)); + } + + axisDetails = nGetAxisDetails(deviceIndex, NPressureAxis); + if(axisDetails.length==0) { + WinTabEnvironmentPlugin.log("NPressureAxis not supported"); + } else { + WinTabEnvironmentPlugin.log("NPressMin: " + axisDetails[0] + ", NPressMax: " + axisDetails[1]); + componentsList.addAll(WinTabComponent.createComponents(context, deviceIndex, NPressureAxis, axisDetails)); + } + + axisDetails = nGetAxisDetails(deviceIndex, TPressureAxis); + if(axisDetails.length==0) { + WinTabEnvironmentPlugin.log("TPressureAxis not supported"); + } else { + WinTabEnvironmentPlugin.log("TPressureAxismin: " + axisDetails[0] + ", TPressureAxismax: " + axisDetails[1]); + componentsList.addAll(WinTabComponent.createComponents(context, deviceIndex, TPressureAxis, axisDetails)); + } + + axisDetails = nGetAxisDetails(deviceIndex, OrientationAxis); + if(axisDetails.length==0) { + WinTabEnvironmentPlugin.log("OrientationAxis not supported"); + } else { + WinTabEnvironmentPlugin.log("OrientationAxis mins/maxs: " + axisDetails[0] + "," + axisDetails[1] + ", " + axisDetails[2] + "," + axisDetails[3] + ", " + axisDetails[4] + "," + axisDetails[5]); + componentsList.addAll(WinTabComponent.createComponents(context, deviceIndex, OrientationAxis, axisDetails)); + } + + axisDetails = nGetAxisDetails(deviceIndex, RotationAxis); + if(axisDetails.length==0) { + WinTabEnvironmentPlugin.log("RotationAxis not supported"); + } else { + WinTabEnvironmentPlugin.log("RotationAxis is supported (by the device, not by this plugin)"); + componentsList.addAll(WinTabComponent.createComponents(context, deviceIndex, RotationAxis, axisDetails)); + } + + String[] cursorNames = nGetCursorNames(deviceIndex); + componentsList.addAll(WinTabComponent.createCursors(context, deviceIndex, cursorNames)); + for(int i=0;i) () -> { + try { + String lib_path = System.getProperty("net.java.games.input.librarypath"); + if (lib_path != null) + System.load(lib_path + File.separator + System.mapLibraryName(lib_name)); + else + System.loadLibrary(lib_name); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + supported = false; + } + return null; + }); + } + + static String getPrivilegedProperty(final String property) { + return AccessController.doPrivileged((PrivilegedAction)() -> System.getProperty(property)); + } + + + static String getPrivilegedProperty(final String property, final String default_value) { + return AccessController.doPrivileged((PrivilegedAction)() -> System.getProperty(property, default_value)); + } + + static { + String osName = getPrivilegedProperty("os.name", "").trim(); + if(osName.startsWith("Windows")) { + supported = true; + loadLibrary("jinput-wintab"); + } + } + + private final Controller[] controllers; + private final List active_devices = new ArrayList<>(); + private final WinTabContext winTabContext; + + /** Creates new DirectInputEnvironment */ + public WinTabEnvironmentPlugin() { + if(isSupported()) { + DummyWindow window; + WinTabContext winTabContext = null; + Controller[] controllers = new Controller[]{}; + try { + window = new DummyWindow(); + winTabContext = new WinTabContext(window); + try { + winTabContext.open(); + controllers = winTabContext.getControllers(); + } catch (Exception e) { + window.destroy(); + throw e; + } + } catch (Exception e) { + log("Failed to enumerate devices: " + e.getMessage()); + e.printStackTrace(); + } + this.controllers = controllers; + this.winTabContext = winTabContext; + AccessController.doPrivileged((PrivilegedAction)() -> { + Runtime.getRuntime().addShutdownHook(new ShutdownHook()); + return null; + }); + } else { + winTabContext = null; + controllers = new Controller[]{}; + } + } + + public boolean isSupported() { + return supported; + } + + public Controller[] getControllers() { + return controllers; + } + + private final class ShutdownHook extends Thread { + public final void run() { + /* Release the devices to kill off active force feedback effects */ + for (int i = 0; i < active_devices.size(); i++) { + // TODO free the devices + } + //Close the context + winTabContext.close(); + /* We won't release the window since it is + * owned by the thread that created the environment. + */ + } + } +} diff --git a/src/plugins/wintab/net/java/games/input/WinTabPacket.java b/src/plugins/wintab/net/java/games/input/WinTabPacket.java new file mode 100644 index 0000000..ae6018d --- /dev/null +++ b/src/plugins/wintab/net/java/games/input/WinTabPacket.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2006 Jeremy Booth (jeremy@newdawnsoftware.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * The name of the author may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + */ +package net.java.games.input; + +public class WinTabPacket { + long PK_TIME; + int PK_X, PK_Y, PK_Z, PK_BUTTONS, PK_NORMAL_PRESSURE, PK_TANGENT_PRESSURE, PK_CURSOR; + int PK_ORIENTATION_ALT, PK_ORIENTATION_AZ, PK_ORIENTATION_TWIST; + + public WinTabPacket() { + + } +} diff --git a/src/samples/samples/net/java/games/TestSample.java b/src/samples/samples/net/java/games/TestSample.java new file mode 100644 index 0000000..4401c23 --- /dev/null +++ b/src/samples/samples/net/java/games/TestSample.java @@ -0,0 +1,58 @@ +/* +Copyright (C) 2021 Herve Girod + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. + +Alternatively if you have any questions about this project, you can visit +the project website at the project page on https://github.com/hervegirod/jinput2 + */ +package samples.net.java.games; + +import net.java.games.input.Component; +import net.java.games.input.Controller; +import net.java.games.input.ControllerEnvironment; + +/** + * + * @since 2.10 + */ +public class TestSample { + public static void main(String[] args) { + Controller[] controllers = ControllerEnvironment.getDefaultEnvironment().getControllers(); + for (int i = 0; i < controllers.length; i++) { + Controller controller = controllers[i]; + System.out.println("********************"); + System.out.println("Name: " + controller.getName()); + System.out.println("Type: " + controller.getType().toString()); + Component[] components = controller.getComponents(); + for (int j = 0; j < components.length; j++) { + Component comp = components[j]; + Component.Identifier ident = comp.getIdentifier(); + System.out.println("Component: " + ident.getName() + " analog: " + comp.isAnalog()); + System.out.println("Component Data: " + comp.getPollData()); + } + } + } +} diff --git a/test/net/java/games/input/DefaultControllerEnvironmentTest.java b/test/net/java/games/input/DefaultControllerEnvironmentTest.java new file mode 100644 index 0000000..2fed767 --- /dev/null +++ b/test/net/java/games/input/DefaultControllerEnvironmentTest.java @@ -0,0 +1,72 @@ +/* +Copyright (C) 2021 Herve Girod + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. + +Alternatively if you have any questions about this project, you can visit +the project website at the project page on https://github.com/hervegirod/jinput2 + */ +package net.java.games.input; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @author Herve + */ +public class DefaultControllerEnvironmentTest { + + public DefaultControllerEnvironmentTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + /** + * Test of getDefaultEnvironment method, of class ControllerEnvironment. + */ + @Test + public void testGetDefaultEnvironment() { + System.out.println("testgetDefaultEnvironment"); + Controller[] controllers = ControllerEnvironment.getDefaultEnvironment().getControllers(); + } + +} diff --git a/test/net/java/games/input/VersionTest.java b/test/net/java/games/input/VersionTest.java new file mode 100644 index 0000000..363ff6a --- /dev/null +++ b/test/net/java/games/input/VersionTest.java @@ -0,0 +1,82 @@ +/* +Copyright (C) 2021 Herve Girod + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. + +Alternatively if you have any questions about this project, you can visit +the project website at the project page on https://github.com/hervegirod/jinput2 + */ +package net.java.games.input; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @since 2.10 + */ +public class VersionTest { + + public VersionTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + /** + * Test of main method, of class Version. + */ + @Test + public void testMain() { + String[] args = new String[1]; + Version.main(args); + } + /** + * Test of getVersion method, of class Version. + */ + @Test + public void testGetVersion() { + System.out.println("VersionTest : testGetVersion"); + String result = Version.getVersion(); + assertEquals("2.10", result); + } + +}